Files
UnrealEngine/Engine/Source/Runtime/AutoRTFM/Public/AutoRTFM.h
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

1575 lines
60 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
/**
* AutoRTFM is designed to make it easy to take existing C++ code--even if it was never designed
* to have any transactional semantics--and make it transactional just by using an alternate compiler.
* For details, see `Engine/Source/Runtime/AutoRTFM/Documentation/README.md`.
*/
// HEADER_UNIT_SKIP - unused warnings
#include "AutoRTFMConstants.h"
#include "AutoRTFMDefines.h" // IWYU pragma: export
#ifdef __cplusplus
#include "AutoRTFMTask.h"
#include <algorithm>
#include <cstdarg>
#include <tuple>
#include <type_traits>
#include <utility>
#endif // __cplusplus
#include <memory.h>
#include <stdlib.h>
#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_during_commit,
autortfm_aborted_by_transact_during_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;
// An opaque unique identifier for a transaction.
typedef uint64_t autortfm_transaction_id;
// 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) AUTORTFM_NOEXCEPT;
#else
UE_AUTORTFM_CRITICAL_INLINE void autortfm_initialize(const autortfm_extern_api* ExternAPI) AUTORTFM_NOEXCEPT
{
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) AUTORTFM_NOEXCEPT;
#else
UE_AUTORTFM_CRITICAL_INLINE bool autortfm_is_closed(void) AUTORTFM_NOEXCEPT
{
return false;
}
#endif
#if UE_AUTORTFM_ENABLED
UE_AUTORTFM_API bool autortfm_is_transactional(void) AUTORTFM_NOEXCEPT;
#else
UE_AUTORTFM_CRITICAL_INLINE bool autortfm_is_transactional(void) AUTORTFM_NOEXCEPT
{
return false;
}
#endif
#if UE_AUTORTFM_ENABLED
UE_AUTORTFM_API bool autortfm_is_committing(void) AUTORTFM_NOEXCEPT;
#else
UE_AUTORTFM_CRITICAL_INLINE bool autortfm_is_committing(void) AUTORTFM_NOEXCEPT
{
return false;
}
#endif
#if UE_AUTORTFM_ENABLED
UE_AUTORTFM_API bool autortfm_is_committing_or_aborting(void) AUTORTFM_NOEXCEPT;
#else
UE_AUTORTFM_CRITICAL_INLINE bool autortfm_is_committing_or_aborting(void) AUTORTFM_NOEXCEPT
{
return false;
}
#endif
#if UE_AUTORTFM_ENABLED
UE_AUTORTFM_API bool autortfm_is_retrying(void) AUTORTFM_NOEXCEPT;
#else
UE_AUTORTFM_CRITICAL_INLINE bool autortfm_is_retrying(void) AUTORTFM_NOEXCEPT
{
return false;
}
#endif
#if UE_AUTORTFM_ENABLED
UE_AUTORTFM_API autortfm_result autortfm_transact(void (*UninstrumentedWork)(void*), void (*InstrumentedWork)(void*), void* Arg) AUTORTFM_EXCEPT;
#else
UE_AUTORTFM_CRITICAL_INLINE autortfm_result autortfm_transact(void (*UninstrumentedWork)(void*), void (*InstrumentedWork)(void*), void* Arg) AUTORTFM_EXCEPT
{
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() AUTORTFM_NOEXCEPT;
#else
UE_AUTORTFM_CRITICAL_INLINE void autortfm_abort_transaction() AUTORTFM_NOEXCEPT {}
#endif
#if UE_AUTORTFM_ENABLED
AUTORTFM_OPEN UE_AUTORTFM_API autortfm_transaction_id autortfm_current_transaction_id() AUTORTFM_NOEXCEPT;
#else
UE_AUTORTFM_CRITICAL_INLINE autortfm_transaction_id autortfm_current_transaction_id() AUTORTFM_NOEXCEPT { return 0; }
#endif
#if UE_AUTORTFM_ENABLED
UE_AUTORTFM_API autortfm_result autortfm_commit_transaction() AUTORTFM_NOEXCEPT;
#else
UE_AUTORTFM_CRITICAL_INLINE autortfm_result autortfm_commit_transaction() AUTORTFM_NOEXCEPT { return autortfm_aborted_by_language; }
#endif
#if UE_AUTORTFM_ENABLED
UE_AUTORTFM_FORCENOINLINE 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_FORCENOINLINE 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) AUTORTFM_NOEXCEPT;
#else
UE_AUTORTFM_CRITICAL_INLINE void autortfm_record_open_write(void* Ptr, size_t Size) AUTORTFM_NOEXCEPT
{
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) AUTORTFM_NOEXCEPT;
#else
UE_AUTORTFM_CRITICAL_INLINE void autortfm_record_open_write_no_memory_validation(void* Ptr, size_t Size) AUTORTFM_NOEXCEPT
{
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 struct 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().
struct 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().
struct autortfm_open_to_closed_table* Next;
};
#if UE_AUTORTFM_ENABLED
UE_AUTORTFM_API void autortfm_register_open_to_closed_functions(struct autortfm_open_to_closed_table* Table) AUTORTFM_NOEXCEPT;
UE_AUTORTFM_API void autortfm_unregister_open_to_closed_functions(struct autortfm_open_to_closed_table* Table) AUTORTFM_NOEXCEPT;
#else
UE_AUTORTFM_CRITICAL_INLINE void autortfm_register_open_to_closed_functions(autortfm_open_to_closed_table* Table) AUTORTFM_NOEXCEPT
{
UE_AUTORTFM_UNUSED(Table);
}
UE_AUTORTFM_CRITICAL_INLINE void autortfm_unregister_open_to_closed_functions(autortfm_open_to_closed_table* Table) AUTORTFM_NOEXCEPT
{
UE_AUTORTFM_UNUSED(Table);
}
#endif
#if UE_AUTORTFM_ENABLED
UE_AUTORTFM_API bool autortfm_is_on_current_transaction_stack(void* Ptr) AUTORTFM_NOEXCEPT;
#else
UE_AUTORTFM_CRITICAL_INLINE bool autortfm_is_on_current_transaction_stack(void* Ptr) AUTORTFM_NOEXCEPT
{
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_pre_abort(void (*work)(void* arg), void* arg) AUTORTFM_NOEXCEPT;
#else
UE_AUTORTFM_CRITICAL_INLINE void autortfm_on_pre_abort(void (*work)(void* arg), void* arg) AUTORTFM_NOEXCEPT
{
UE_AUTORTFM_UNUSED(work);
UE_AUTORTFM_UNUSED(arg);
}
#endif
#if UE_AUTORTFM_ENABLED
UE_AUTORTFM_API void autortfm_on_abort(void (*work)(void* arg), void* arg) AUTORTFM_NOEXCEPT;
#else
UE_AUTORTFM_CRITICAL_INLINE void autortfm_on_abort(void (*work)(void* arg), void* arg) AUTORTFM_NOEXCEPT
{
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) AUTORTFM_NOEXCEPT;
UE_AUTORTFM_API void autortfm_pop_on_abort_handler(const void* key) AUTORTFM_NOEXCEPT;
#else
UE_AUTORTFM_CRITICAL_INLINE void autortfm_push_on_abort_handler(const void* key, void (*work)(void* arg), void* arg) AUTORTFM_NOEXCEPT
{
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) AUTORTFM_NOEXCEPT
{
UE_AUTORTFM_UNUSED(key);
}
#endif
#if UE_AUTORTFM_ENABLED
UE_AUTORTFM_API void* autortfm_did_allocate(void* ptr, size_t size) AUTORTFM_NOEXCEPT;
#else
UE_AUTORTFM_CRITICAL_INLINE void* autortfm_did_allocate(void* ptr, size_t size) AUTORTFM_NOEXCEPT
{
UE_AUTORTFM_UNUSED(size);
return ptr;
}
#endif
#if UE_AUTORTFM_ENABLED
UE_AUTORTFM_API void autortfm_did_free(void* ptr) AUTORTFM_NOEXCEPT;
#else
UE_AUTORTFM_CRITICAL_INLINE void autortfm_did_free(void* ptr) AUTORTFM_NOEXCEPT
{
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) AUTORTFM_NOEXCEPT;
#else
UE_AUTORTFM_CRITICAL_INLINE void autortfm_check_abi(void* ptr, size_t size) AUTORTFM_NOEXCEPT
{
UE_AUTORTFM_UNUSED(ptr);
UE_AUTORTFM_UNUSED(size);
}
#endif
#if UE_AUTORTFM_ENABLED
// Called when execution unexpectedly reaches a code path that was considered unreachable.
// Either aborts execution of the program or aborts the current transaction, depending on the
// current 'InternalAbortAction' state.
[[noreturn]] UE_AUTORTFM_API void autortfm_unreachable(const char* Message) AUTORTFM_NOEXCEPT;
#else
UE_AUTORTFM_CRITICAL_INLINE void autortfm_unreachable(const char* Message) AUTORTFM_NOEXCEPT
{
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 a new transaction nest was attempted (via OnCommit or OnComplete) while the
// current transaction was being committed.
AbortedByTransactDuringCommit = autortfm_aborted_by_transact_during_commit,
// The transaction aborted because a new transaction nest was attempted (via OnAbort or OnComplete) while the
// current transaction was being aborted.
AbortedByTransactDuringAbort = autortfm_aborted_by_transact_during_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,
};
// Returns true if Status represents one of the "aborting" states.
inline bool IsStatusAborting(EContextStatus Status)
{
switch (Status)
{
case EContextStatus::AbortedByFailedLockAcquisition:
case EContextStatus::AbortedByLanguage:
case EContextStatus::AbortedByRequest:
case EContextStatus::AbortedByCascadingAbort:
case EContextStatus::AbortedByCascadingRetry:
return true;
case EContextStatus::Idle:
case EContextStatus::OnTrack:
case EContextStatus::Committing:
case EContextStatus::InStaticLocalInitializer:
return false;
}
}
// An opaque unique identifier for a transaction.
using TransactionID = autortfm_transaction_id;
// 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();
UE_AUTORTFM_API void CascadingRetryTransactionInternal();
UE_AUTORTFM_API void OnCommitInternal(TTask<void()>&& Work);
UE_AUTORTFM_API void OnPreAbortInternal(TTask<void()>&& Work);
UE_AUTORTFM_API void OnAbortInternal(TTask<void()>&& Work);
UE_AUTORTFM_API void OnCompleteInternal(TTask<void()>&& Work);
UE_AUTORTFM_API void PushOnCommitHandlerInternal(const void* Key, TTask<void()>&& 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<void()>&& Work);
UE_AUTORTFM_API void PopOnAbortHandlerInternal(const void* Key);
UE_AUTORTFM_API void PopAllOnAbortHandlersInternal(const void* Key);
UE_AUTORTFM_API void RegisterOnCommitFromTheOpen(TTask<void()>&& Work);
UE_AUTORTFM_API void RegisterOnAbortFromTheOpen(TTask<void()>&& Work);
UE_AUTORTFM_API void RedirectedLoad(uint32_t AddressSpace, void* DestPointer, uint64_t Size, uint64_t SourceAddress);
UE_AUTORTFM_API void RedirectedStore(uint32_t AddressSpace, uint64_t DestAddress, uint64_t Size, const void* SourcePointer);
} // namespace ForTheRuntime
#endif
template<typename FunctorType>
void AutoRTFMFunctorInvoker(void* Arg) { (*static_cast<const FunctorType*>(Arg))(); }
#if UE_AUTORTFM
extern "C" UE_AUTORTFM_FORCENOINLINE UE_AUTORTFM_API void* autortfm_lookup_function(void* OriginalFunction, const char* Where) AUTORTFM_NOEXCEPT;
template<typename FunctorType>
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<void(*)(void*)>(autortfm_lookup_function(reinterpret_cast<void*>(&AutoRTFMFunctorInvoker<FunctorType>), "AutoRTFMLookupInstrumentedFunctorInvoker"));
return Result;
}
#else
template<typename FunctorType>
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_ALWAYS 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_ALWAYS bool IsClosed() { return autortfm_is_closed(); }
// Tells us if we are currently committing a transaction. This will return true
// when inside an on-commit handler.
UE_AUTORTFM_CRITICAL_INLINE bool IsCommitting() { return autortfm_is_committing(); }
// Tells us if we are currently committing or aborting a transaction. This will return true
// when inside an on-abort, on-commit or on-complete handler.
UE_AUTORTFM_CRITICAL_INLINE bool IsCommittingOrAborting() { return autortfm_is_committing_or_aborting(); }
// Tells us if we are currently retrying a transaction. This will return true when inside an
// on-abort or on-complete handler that was triggered by CascadingRetryTransaction.
UE_AUTORTFM_CRITICAL_INLINE bool IsRetrying() { return autortfm_is_retrying(); }
// 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);
}
// Returns an opaque identifier for the current transaction.
UE_AUTORTFM_CRITICAL_INLINE TransactionID CurrentTransactionID()
{
return autortfm_current_transaction_id();
}
// 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<typename FunctorType>
UE_AUTORTFM_CRITICAL_INLINE ETransactionResult Transact(const FunctorType& Functor)
{
ETransactionResult Result =
static_cast<ETransactionResult>(
autortfm_transact(
&AutoRTFMFunctorInvoker<FunctorType>,
AutoRTFMLookupInstrumentedFunctorInvoker<FunctorType>(Functor),
const_cast<void*>(static_cast<const void*>(&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<typename FunctorType>
UE_AUTORTFM_CRITICAL_INLINE ETransactionResult TransactThenOpen(const FunctorType& Functor)
{
ETransactionResult Result =
static_cast<ETransactionResult>(
autortfm_transact_then_open(
&AutoRTFMFunctorInvoker<FunctorType>,
AutoRTFMLookupInstrumentedFunctorInvoker<FunctorType>(Functor),
const_cast<void*>(static_cast<const void*>(&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<typename FunctorType>
UE_AUTORTFM_CRITICAL_INLINE void Commit(const FunctorType& Functor)
{
autortfm_commit(
&AutoRTFMFunctorInvoker<FunctorType>,
AutoRTFMLookupInstrumentedFunctorInvoker<FunctorType>(Functor),
const_cast<void*>(static_cast<const void*>(&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, on-complete callbacks will
// be invoked. Finally, control will be returned to the end of the outermost
// Transact.
#if UE_AUTORTFM
UE_AUTORTFM_CRITICAL_INLINE void CascadingAbortTransaction()
{
ForTheRuntime::CascadingAbortTransactionInternal();
}
#else
UE_AUTORTFM_CRITICAL_INLINE void CascadingAbortTransaction() {}
#endif
namespace Detail
{
template<typename T, typename = void>
struct THasAssignFromOpenToClosedMethod : std::false_type {};
template<typename T>
struct THasAssignFromOpenToClosedMethod<T, std::void_t<decltype(T::AutoRTFMAssignFromOpenToClosed(std::declval<T&>(), std::declval<T>()))>> : 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<typename T>
static constexpr bool HasAssignFromOpenToClosedMethod = Detail::THasAssignFromOpenToClosedMethod<T>::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<typename T, typename = void>
struct TAssignFromOpenToClosed;
namespace Detail
{
template<typename T, typename = void>
struct THasAssignFromOpenToClosedTrait : std::false_type {};
template<typename T>
struct THasAssignFromOpenToClosedTrait<T, std::void_t<decltype(TAssignFromOpenToClosed<T>::Assign(std::declval<T&>(), std::declval<T>()))>> : std::true_type {};
}
// Evaluates to true if the type T supports assigning from open to closed transactions.
template<typename T>
static constexpr bool HasAssignFromOpenToClosedTrait = Detail::THasAssignFromOpenToClosedTrait<T>::value;
// Specialization of TAssignFromOpenToClosed for fundamental types.
template<typename T>
struct TAssignFromOpenToClosed<T, std::enable_if_t<std::is_fundamental_v<T>>>
{
UE_AUTORTFM_FORCEINLINE static void Assign(T& Closed, T Open) { Closed = Open; }
};
// Specialization of TAssignFromOpenToClosed for raw pointer types.
template<typename T>
struct TAssignFromOpenToClosed<T*, void>
{
UE_AUTORTFM_FORCEINLINE static void Assign(T*& Closed, T* Open) { Closed = Open; }
};
// Specialization of TAssignFromOpenToClosed for std::tuple.
template<typename ... TYPES>
struct TAssignFromOpenToClosed<std::tuple<TYPES...>, std::enable_if_t<(HasAssignFromOpenToClosedTrait<TYPES> && ...)>>
{
template<size_t I = 0, typename SRC = void>
UE_AUTORTFM_FORCEINLINE static void AssignElements(std::tuple<TYPES...>& Closed, SRC&& Open)
{
if constexpr(I < sizeof...(TYPES))
{
using E = std::tuple_element_t<I, std::tuple<TYPES...>>;
TAssignFromOpenToClosed<E>::Assign(std::get<I>(Closed), std::get<I>(std::forward<SRC>(Open)));
AssignElements<I+1>(Closed, std::forward<SRC>(Open));
}
}
template<typename SRC>
UE_AUTORTFM_FORCEINLINE static void Assign(std::tuple<TYPES...>& Closed, SRC&& Open)
{
AssignElements(Closed, std::forward<SRC>(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<typename T>
struct TAssignFromOpenToClosed<T, std::enable_if_t<HasAssignFromOpenToClosedMethod<T>>>
{
template<typename OPEN>
UE_AUTORTFM_FORCEINLINE static void Assign(T& Closed, OPEN&& Open)
{
Closed.AutoRTFMAssignFromOpenToClosed(Closed, std::forward<OPEN>(Open));
}
};
// Specialization of TAssignFromOpenToClosed for `void` (used to make IsSafeToReturnFromOpen<void> work).
template<>
struct TAssignFromOpenToClosed<void, void>;
// Evaluates to true if the type T is safe to return from Open().
template<typename T>
static constexpr bool IsSafeToReturnFromOpen = HasAssignFromOpenToClosedTrait<T> || std::is_same_v<T, void>;
// 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<FunctorType>()())
>
UE_AUTORTFM_CRITICAL_INLINE ReturnType Open(const FunctorType& Functor)
{
static_assert(IsSafeToReturnFromOpen<ReturnType>,
"function return type is not safe to return from Open()");
#if UE_AUTORTFM
if (!autortfm_is_closed())
{
return Functor();
}
if constexpr (IsSafeToReturnFromOpen<ReturnType>)
{
if constexpr (std::is_same_v<void, ReturnType>)
{
struct FCallHelper
{
AUTORTFM_DISABLE static void Call(void* Arg)
{
const FunctorType& Fn = *reinterpret_cast<FunctorType*>(Arg);
UE_AUTORTFM_CALLSITE_FORCEINLINE Fn();
}
};
if constexpr (VALIDATION_LEVEL == EMemoryValidationLevel::Default)
{
autortfm_open(&FCallHelper::Call, const_cast<void*>(reinterpret_cast<const void*>(&Functor)), __builtin_return_address(0));
}
else
{
autortfm_open_explicit_validation(
static_cast<autortfm_memory_validation_level>(VALIDATION_LEVEL),
&FCallHelper::Call,
const_cast<void*>(static_cast<const void*>(&Functor)),
__builtin_return_address(0));
}
}
else
{
struct FCallHelper
{
AUTORTFM_DISABLE static void Call(void* Arg)
{
FCallHelper& Self = *reinterpret_cast<FCallHelper*>(Arg);
UE_AUTORTFM_CALLSITE_FORCEINLINE
TAssignFromOpenToClosed<ReturnType>::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<void*>(&Helper), __builtin_return_address(0));
}
else
{
autortfm_open_explicit_validation(
static_cast<autortfm_memory_validation_level>(VALIDATION_LEVEL),
&FCallHelper::Call,
reinterpret_cast<void*>(&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.
//
// If Close() returns an aborting status (see IsStatusAborting()), then
// attempting to use the transaction is undefined behaviour. The caller should
// return to the closed as quickly as possible to avoid the risk of the
// transaction being used in its rolled-back state.
template<typename FunctorType> [[nodiscard]] UE_AUTORTFM_CRITICAL_INLINE EContextStatus Close(const FunctorType& Functor)
{
return static_cast<EContextStatus>(
autortfm_close(
&AutoRTFMFunctorInvoker<FunctorType>,
AutoRTFMLookupInstrumentedFunctorInvoker<FunctorType>(Functor),
const_cast<void*>(static_cast<const void*>(&Functor))));
}
// Force a transaction nest to be retried. Once the transaction has aborted,
// all on-complete handlers will be called 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, the call is ignored.
#if UE_AUTORTFM
UE_AUTORTFM_CRITICAL_INLINE void CascadingRetryTransaction()
{
if (autortfm_is_closed())
{
ForTheRuntime::CascadingRetryTransactionInternal();
}
}
#else
UE_AUTORTFM_CRITICAL_INLINE void CascadingRetryTransaction()
{
}
#endif
#if UE_AUTORTFM
// Have some work happen when this transaction commits.
// In a nested transaction, the work is deferred until the outermost nest is committed;
// at that point, the worklist is run in FIFO order.
// If this is called outside a transaction or from an open nest, then the work
// happens immediately.
template<typename FunctorType> UE_AUTORTFM_CRITICAL_INLINE void OnCommit(FunctorType&& Work)
{
if (autortfm_is_closed())
{
ForTheRuntime::OnCommitInternal(std::forward<FunctorType>(Work));
}
else
{
UE_AUTORTFM_CALLSITE_FORCEINLINE Work();
}
}
#else
template<typename FunctorType> UE_AUTORTFM_CRITICAL_INLINE void OnCommit(FunctorType&& Work) { Work(); }
#endif
#if UE_AUTORTFM
// Have some work happen when this transaction aborts (before memory rollback).
// If an abort occurs, the work list is run in LIFO order.
// If this is called outside a transaction or from an open nest then the work is ignored.
template<typename FunctorType> UE_AUTORTFM_CRITICAL_INLINE void OnPreAbort(FunctorType&& Work)
{
if (autortfm_is_closed())
{
ForTheRuntime::OnPreAbortInternal(std::forward<FunctorType>(Work));
}
}
#else
template<typename FunctorType> UE_AUTORTFM_CRITICAL_INLINE void OnPreAbort(FunctorType&&) {}
#endif
#if UE_AUTORTFM
// Have some work happen when this transaction aborts (after memory rollback).
// If an abort occurs, the work list is run in LIFO order.
// If this is called outside a transaction or from an open nest then the work is ignored.
template<typename FunctorType> UE_AUTORTFM_CRITICAL_INLINE void OnAbort(FunctorType&& Work)
{
if (autortfm_is_closed())
{
ForTheRuntime::OnAbortInternal(std::forward<FunctorType>(Work));
}
}
#else
template<typename FunctorType> UE_AUTORTFM_CRITICAL_INLINE void OnAbort(FunctorType&&) {}
#endif
#if UE_AUTORTFM
// Have some work happen when the transaction completes, whether that transaction is committed or aborted.
// In a nested transaction, the work is deferred until the very end of the outermost nest, right before
// the end of the transaction.
// The worklist is run in FIFO order, after the OnCommit or OnAbort worklist has finished.
// If this is called outside a transaction or from an open nest, then the work is ignored.
template<typename FunctorType> UE_AUTORTFM_CRITICAL_INLINE void OnComplete(FunctorType&& Work)
{
if (autortfm_is_closed())
{
ForTheRuntime::OnCompleteInternal(std::forward<FunctorType>(Work));
}
}
#else
template<typename FunctorType> UE_AUTORTFM_CRITICAL_INLINE void OnComplete(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<typename FunctorType> UE_AUTORTFM_CRITICAL_INLINE void PushOnCommitHandler(const void* Key, FunctorType&& Work)
{
if (autortfm_is_closed())
{
ForTheRuntime::PushOnCommitHandlerInternal(Key, std::forward<FunctorType>(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<typename FunctorType> 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<typename FunctorType> UE_AUTORTFM_CRITICAL_INLINE void PushOnAbortHandler(const void* Key, FunctorType&& Work)
{
if (autortfm_is_closed())
{
ForTheRuntime::PushOnAbortHandlerInternal(Key, std::forward<FunctorType>(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<typename FunctorType> 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
struct FHeapRedirectCallbacks
{
uint32_t AddressSpace;
void (*RedirectedLoad)(void* DestPointer, uint64_t Size, uint64_t SourceAddress);
void (*RedirectedStore)(uint64_t DestAddress, uint64_t Size, const void* SourcePointer);
};
#if UE_AUTORTFM
void RegisterHeapRedirectCallbacks(FHeapRedirectCallbacks Callbacks);
#else
UE_AUTORTFM_CRITICAL_INLINE void RegisterHeapRedirectCallbacks(FHeapRedirectCallbacks /*Callbacks*/)
{
}
#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<typename Type> 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<typename Type> 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.
#if UE_AUTORTFM_ENABLED
[[noreturn]] UE_AUTORTFM_CRITICAL_INLINE_ALWAYS void Unreachable(const char* Message = nullptr)
{
autortfm_unreachable(Message);
}
#else
UE_AUTORTFM_CRITICAL_INLINE void Unreachable(const char* Message = nullptr)
{
UE_AUTORTFM_UNUSED(Message);
}
#endif
// If we are running within a transaction, call `AutoRTFM::Unreachable`.
UE_AUTORTFM_CRITICAL_INLINE_ALWAYS void UnreachableIfTransactional(const char* Message = nullptr)
{
if (AutoRTFM::IsTransactional())
{
AutoRTFM::Unreachable(Message);
}
}
// If we are running within a closed transaction, call `AutoRTFM::Unreachable`.
UE_AUTORTFM_CRITICAL_INLINE_ALWAYS void UnreachableIfClosed(const char* Message = nullptr)
{
if (AutoRTFM::IsClosed())
{
AutoRTFM::Unreachable(Message);
}
}
// Evaluates to true if a call to the function with type FuncType with arguments
// of the given types resolves to a function overload that is AutoRTFM-disabled.
// Warning: This is an experimental API and may be removed in the future.
template <typename FuncType, typename ... ArgTypes>
static constexpr bool CallIsDisabled = AUTORTFM_CALL_IS_DISABLED(std::declval<FuncType>()(std::declval<ArgTypes>()...));
// Evaluates to true if the destructor of type Type is AutoRTFM-disabled.
// Warning: This is an experimental API and may be removed in the future.
template <typename Type>
static constexpr bool DestructorIsDisabled = AUTORTFM_CALL_IS_DISABLED(std::declval<Type>().~Type());
// Evaluates to true if the constructor of Type with the given arguments is AutoRTFM-disabled.
// Warning: This is an experimental API and may be removed in the future.
template <typename Type, typename ... ArgTypes>
static constexpr bool ConstructorIsDisabled = AUTORTFM_CALL_IS_DISABLED(::new Type(std::declval<ArgTypes>()...));
// 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).
AUTORTFM_DISABLE UE_AUTORTFM_API void StartTransaction();
// 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).
AUTORTFM_DISABLE UE_AUTORTFM_API ETransactionResult CommitTransaction();
// Manually clear the status of a user abort from the top transaction in a nest.
AUTORTFM_DISABLE UE_AUTORTFM_API void ClearTransactionStatus();
// Returns the current transaction status.
AUTORTFM_DISABLE UE_AUTORTFM_API EContextStatus GetContextStatus();
// RollbackTransaction() aborts the current transaction.
// If the transaction is scoped, then this is kept on the transaction stack
// until we return to the closed. Attempting to use this transaction before
// transitioning to the closed is undefined behaviour.
// If the transaction is unscoped, then this is popped immediately.
// RollbackTransaction() sets the transaction status to AbortedByRequest.
AUTORTFM_DISABLE UE_AUTORTFM_API void RollbackTransaction();
// CascadingAbortRollbackTransaction() rolls back the current scoped
// transaction, which must be a scoped transaction (i.e. created by
// AutoRTFM::Transact() and not by StartTransaction()).
// CascadingAbortRollbackTransaction() sets the transaction status to
// AbortedByCascadingAbort, which will cause the next open -> closed
// transition point to abort all transactions on the transaction nest.
// Attempting to use any transactions in the open while unwinding is
// undefined behaviour.
AUTORTFM_DISABLE UE_AUTORTFM_API void CascadingAbortRollbackTransaction();
// CascadingRetryRollbackTransaction() rolls back the current scoped
// transaction, which must be a scoped transaction (i.e. created by
// AutoRTFM::Transact() and not by StartTransaction()).
// CascadingRetryRollbackTransaction() sets the transaction status to
// AbortedByCascadingRetry, which will cause the next open -> closed
// transition point to abort all transactions on the transaction nest,
// and then retry the outermost transaction.
// Attempting to use any transactions in the open while unwinding is
// undefined behaviour.
AUTORTFM_DISABLE UE_AUTORTFM_API void CascadingRetryRollbackTransaction();
// Reserved for future.
UE_AUTORTFM_CRITICAL_INLINE void RecordOpenRead(void const*, size_t) {}
// Reserved for future.
template<typename Type> 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<typename FunctorType>
UE_AUTORTFM_FORCEINLINE void operator+(FunctorType&& F)
{
AutoRTFM::Open(std::forward<FunctorType>(F));
}
};
struct FOpenNoMemoryValidationHelper
{
template<typename FunctorType>
UE_AUTORTFM_FORCEINLINE void operator+(FunctorType&& F)
{
AutoRTFM::Open<EMemoryValidationLevel::Disabled>(std::forward<FunctorType>(F));
}
};
struct FCloseHelper
{
template<typename FunctorType>
[[nodiscard]] EContextStatus operator+(FunctorType F)
{
return AutoRTFM::Close(std::move(F));
}
};
struct FOnPreAbortHelper
{
template<typename FunctorType>
UE_AUTORTFM_FORCEINLINE void operator+(FunctorType&& F)
{
AutoRTFM::OnPreAbort(std::forward<FunctorType>(F));
}
};
struct FOnAbortHelper
{
template<typename FunctorType>
UE_AUTORTFM_FORCEINLINE void operator+(FunctorType&& F)
{
AutoRTFM::OnAbort(std::forward<FunctorType>(F));
}
};
struct FOnCommitHelper
{
template<typename FunctorType>
UE_AUTORTFM_FORCEINLINE void operator+(FunctorType&& F)
{
AutoRTFM::OnCommit(std::forward<FunctorType>(F));
}
};
struct FTransactHelper
{
template<typename FunctorType>
UE_AUTORTFM_FORCEINLINE void operator+(FunctorType&& F)
{
AutoRTFM::Transact(std::forward<FunctorType>(F));
}
};
namespace /* must have internal linkage */
{
struct FThreadLocalHelper
{
template <typename Type, int Unique>
UE_AUTORTFM_ALWAYS_OPEN static Type& Get()
{
thread_local Type Data;
return Data;
}
};
}
} // namespace AutoRTFM::Private
#define UE_AUTORTFM_DECLARE_THREAD_LOCAL_VAR_IMPL(Type, Name) Type& Name = ::AutoRTFM::Private::FThreadLocalHelper::Get<Type, __COUNTER__>()
#define UE_AUTORTFM_OPEN_IMPL ::AutoRTFM::Private::FOpenHelper{} + [&]()
#define UE_AUTORTFM_OPEN_NO_VALIDATION_IMPL ::AutoRTFM::Private::FOpenNoMemoryValidationHelper{} + [&]()
#define UE_AUTORTFM_CLOSE_IMPL ::AutoRTFM::Private::FCloseHelper{} + [&]()
#define UE_AUTORTFM_ONPREABORT_IMPL(...) ::AutoRTFM::Private::FOnPreAbortHelper{} + [__VA_ARGS__]() mutable
#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_CLOSE_IMPL
#define UE_AUTORTFM_ONPREABORT_IMPL(...) while (false)
#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 in the closed, transactionally. Anything performed in the closed will be undone if a transaction fails.
// Calls should be written like this: UE_AUTORTFM_CLOSE { ... code ... };
#define UE_AUTORTFM_CLOSE UE_AUTORTFM_CLOSE_IMPL
// Runs a block of code if a transaction aborts (before memory rollback).
// 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_ONPREABORT(=) { ... code ... };
#define UE_AUTORTFM_ONPREABORT(...) UE_AUTORTFM_ONPREABORT_IMPL(__VA_ARGS__)
// Runs a block of code if a transaction aborts (after memory rollback).
// 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
#endif // __cplusplus