// Copyright Epic Games, Inc. All Rights Reserved. #include "AutoRTFM.h" #include "Context.h" #include "ExternAPI.h" #include "Transaction.h" #include #include #include #if UE_AUTORTFM static_assert(UE_AUTORTFM_ENABLED, "AutoRTFM/API.cpp requires the compiler flag '-fautortfm'"); namespace { static constexpr AutoRTFM::EMemoryValidationLevel GDefaultMemoryValidationLevel = AutoRTFM::EMemoryValidationLevel::Disabled; // Move this to a local only and use functions to access this #if UE_AUTORTFM_ENABLED_RUNTIME_BY_DEFAULT int GAutoRTFMRuntimeEnabled = AutoRTFM::ForTheRuntime::EAutoRTFMEnabledState::AutoRTFM_EnabledByDefault; #else int GAutoRTFMRuntimeEnabled = AutoRTFM::ForTheRuntime::EAutoRTFMEnabledState::AutoRTFM_DisabledByDefault; #endif // UE_AUTORTFM_ENABLED_RUNTIME_BY_DEFAULT int GAutoRTFMInternalAbortAction = AutoRTFM::ForTheRuntime::EAutoRTFMInternalAbortActionState::Crash; int GAutoRTFMRetryTransactions = AutoRTFM::ForTheRuntime::EAutoRTFMRetryTransactionState::NoRetry; // Set the percentage chance [0..100] that AutoRTFM will be enabled. // 100.0 means that AutoRTFM will always be enabled; 1.0 means that AutoRTFM has a 1% chance of being enabled. // See `AutoRTFM::ForTheRuntime::CoinTossDisable` for implementation details. float GAutoRTFMEnabledProbability = 100.0f; // Note: GMemoryValidationLevel should never be EMemoryValidationLevel::Default. // EMemoryValidationLevel::Default is a special enumerator that can be used // in the public API to map to GDefaultMemoryValidationLevel. AutoRTFM::EMemoryValidationLevel GMemoryValidationLevel = GDefaultMemoryValidationLevel; bool GMemoryValidationThrottlingEnabled = true; bool GMemoryValidationStatisticsEnabled = true; bool GAutoRTFMEnsureOnInternalAbort = true; // A linked-list of open->closed function tables populated by // autortfm_register_open_to_closed_functions(). This is consumed by // ProcessAllPendingOpenToClosedRegistrations() when autortfm_initialize() // is called. autortfm_open_to_closed_table* GPendingOpenToClosedRegistrations = nullptr; } #endif // UE_AUTORTFM namespace AutoRTFM { namespace Testing { UE_AUTORTFM_API ForTheRuntime::EAutoRTFMEnabledState ForceSetAutoRTFMRuntime(ForTheRuntime::EAutoRTFMEnabledState State) { #if UE_AUTORTFM const int Original = GAutoRTFMRuntimeEnabled; if (GAutoRTFMRuntimeEnabled != State) { GAutoRTFMRuntimeEnabled = State; if (GExternAPI.OnRuntimeEnabledChanged) { GExternAPI.OnRuntimeEnabledChanged(); } } return static_cast(Original); #else UE_AUTORTFM_UNUSED(State); return ForTheRuntime::AutoRTFM_ForcedDisabled; #endif } } // Testing } // AutoRTFM namespace AutoRTFM { namespace ForTheRuntime { bool SetAutoRTFMRuntime(EAutoRTFMEnabledState State) { // #noop if AutoRTFM is not compiled in #if UE_AUTORTFM auto Stringify = [](int Query) -> const char* { switch (Query) { default: InternalUnreachable(); #define HANDLE_CASE(x) case x: return #x HANDLE_CASE(EAutoRTFMEnabledState::AutoRTFM_ForcedEnabled); HANDLE_CASE(EAutoRTFMEnabledState::AutoRTFM_ForcedDisabled); HANDLE_CASE(EAutoRTFMEnabledState::AutoRTFM_OverriddenEnabled); HANDLE_CASE(EAutoRTFMEnabledState::AutoRTFM_OverriddenDisabled); HANDLE_CASE(EAutoRTFMEnabledState::AutoRTFM_Enabled); HANDLE_CASE(EAutoRTFMEnabledState::AutoRTFM_Disabled); HANDLE_CASE(EAutoRTFMEnabledState::AutoRTFM_EnabledByDefault); HANDLE_CASE(EAutoRTFMEnabledState::AutoRTFM_DisabledByDefault); #undef HANDLE_CASE } }; auto DoIgnoreLog = [&](int State, int Stored) { AUTORTFM_LOG("Ignoring changing AutoRTFM runtime state to '%s' as it was previously set to '%s'", Stringify(State), Stringify(Stored)); }; switch (GAutoRTFMRuntimeEnabled) { default: break; case EAutoRTFMEnabledState::AutoRTFM_ForcedEnabled: case EAutoRTFMEnabledState::AutoRTFM_ForcedDisabled: DoIgnoreLog(State, GAutoRTFMRuntimeEnabled); return false; } switch (GAutoRTFMRuntimeEnabled) { default: break; case EAutoRTFMEnabledState::AutoRTFM_OverriddenEnabled: case EAutoRTFMEnabledState::AutoRTFM_OverriddenDisabled: if ((State == EAutoRTFMEnabledState::AutoRTFM_Enabled) || (State == EAutoRTFMEnabledState::AutoRTFM_Disabled) || (State == EAutoRTFMEnabledState::AutoRTFM_EnabledByDefault) || (State == EAutoRTFMEnabledState::AutoRTFM_DisabledByDefault)) { DoIgnoreLog(State, GAutoRTFMRuntimeEnabled); return false; } case EAutoRTFMEnabledState::AutoRTFM_Enabled: case EAutoRTFMEnabledState::AutoRTFM_Disabled: if ((State == EAutoRTFMEnabledState::AutoRTFM_EnabledByDefault) || (State == EAutoRTFMEnabledState::AutoRTFM_DisabledByDefault)) { DoIgnoreLog(State, GAutoRTFMRuntimeEnabled); return false; } } if (GAutoRTFMRuntimeEnabled != State) { GAutoRTFMRuntimeEnabled = State; if (GExternAPI.OnRuntimeEnabledChanged) { GExternAPI.OnRuntimeEnabledChanged(); } } return true; #else UE_AUTORTFM_UNUSED(State); return false; #endif } bool IsAutoRTFMRuntimeEnabledInternal() { // #noop if AutoRTFM is not compiled in #if UE_AUTORTFM switch (GAutoRTFMRuntimeEnabled) { default: return false; case EAutoRTFMEnabledState::AutoRTFM_Enabled: case EAutoRTFMEnabledState::AutoRTFM_ForcedEnabled: case EAutoRTFMEnabledState::AutoRTFM_OverriddenEnabled: case EAutoRTFMEnabledState::AutoRTFM_EnabledByDefault: return true; } #else return false; #endif } void SetAutoRTFMEnabledProbability(float Chance) { #if UE_AUTORTFM AUTORTFM_ASSERT(Chance >= 0.0f && Chance <= 100.0f); GAutoRTFMEnabledProbability = Chance; #else UE_AUTORTFM_UNUSED(Chance); #endif } float GetAutoRTFMEnabledProbability() { #if UE_AUTORTFM return GAutoRTFMEnabledProbability; #else return 0.0f; #endif } bool CoinTossDisable() { #if UE_AUTORTFM if (!IsAutoRTFMRuntimeEnabled()) { return false; } static std::random_device Device; static std::mt19937 Generator(Device()); static std::uniform_real_distribution Distribution(0.0f, 100.0f); // A value in the range [0..100), EG. inclusive of 0, exclusive of 100. // So a `GAutoRTFMEnabledProbability` of 100 is always greater than the // potential random range, and `GAutoRTFMEnabledProbability` of 0 is // always less than or equal to the range. const float Random = Distribution(Generator); if (GAutoRTFMEnabledProbability <= Random) { // If we have the runtime cvar set to `ForcedEnabled` then it'll ignore this call! return AutoRTFM::ForTheRuntime::SetAutoRTFMRuntime(AutoRTFM::ForTheRuntime::AutoRTFM_ForcedDisabled); } #endif return false; } void SetInternalAbortAction(EAutoRTFMInternalAbortActionState State) { #if UE_AUTORTFM GAutoRTFMInternalAbortAction = State; #else UE_AUTORTFM_UNUSED(State); #endif } EAutoRTFMInternalAbortActionState GetInternalAbortAction() { #if UE_AUTORTFM return static_cast(GAutoRTFMInternalAbortAction); #else return Crash; #endif } bool GetEnsureOnInternalAbort() { #if UE_AUTORTFM return GAutoRTFMEnsureOnInternalAbort; #else return false; #endif } void SetEnsureOnInternalAbort(bool bEnabled) { #if UE_AUTORTFM GAutoRTFMEnsureOnInternalAbort = bEnabled; #else UE_AUTORTFM_UNUSED(bEnabled); #endif } void SetRetryTransaction(EAutoRTFMRetryTransactionState State) { #if UE_AUTORTFM if (GAutoRTFMRetryTransactions != State) { GAutoRTFMRetryTransactions = State; if (GExternAPI.OnRetryTransactionsChanged) { GExternAPI.OnRetryTransactionsChanged(); } } #else UE_AUTORTFM_UNUSED(State); #endif } EAutoRTFMRetryTransactionState GetRetryTransaction() { #if UE_AUTORTFM return static_cast(GAutoRTFMRetryTransactions); #else return NoRetry; #endif } bool ShouldRetryNonNestedTransactions() { #if UE_AUTORTFM switch (GAutoRTFMRetryTransactions) { default: return false; case EAutoRTFMRetryTransactionState::RetryNonNested: case EAutoRTFMRetryTransactionState::RetryNestedToo: return true; } #else return false; #endif } bool ShouldRetryNestedTransactionsToo() { #if UE_AUTORTFM switch (GAutoRTFMRetryTransactions) { default: return false; case EAutoRTFMRetryTransactionState::RetryNestedToo: return true; } #else return false; #endif } EMemoryValidationLevel GetMemoryValidationLevel() { #if UE_AUTORTFM return GMemoryValidationLevel; #else return EMemoryValidationLevel::Disabled; #endif } void SetMemoryValidationLevel(EMemoryValidationLevel Level) { #if UE_AUTORTFM if (Level == EMemoryValidationLevel::Default) { Level = GDefaultMemoryValidationLevel; } if (GMemoryValidationLevel != Level) { GMemoryValidationLevel = Level; if (GExternAPI.OnMemoryValidationLevelChanged) { GExternAPI.OnMemoryValidationLevelChanged(); } } #else UE_AUTORTFM_UNUSED(Level); #endif } bool GetMemoryValidationThrottlingEnabled() { #if UE_AUTORTFM return GMemoryValidationThrottlingEnabled; #else return false; #endif } void SetMemoryValidationThrottlingEnabled(bool bEnabled) { #if UE_AUTORTFM if (GMemoryValidationThrottlingEnabled != bEnabled) { GMemoryValidationThrottlingEnabled = bEnabled; if (GExternAPI.OnMemoryValidationThrottlingChanged) { GExternAPI.OnMemoryValidationThrottlingChanged(); } } #else UE_AUTORTFM_UNUSED(bEnabled); #endif } bool GetMemoryValidationStatisticsEnabled() { #if UE_AUTORTFM return GMemoryValidationStatisticsEnabled; #else return false; #endif } void SetMemoryValidationStatisticsEnabled(bool bEnabled) { #if UE_AUTORTFM if (GMemoryValidationStatisticsEnabled != bEnabled) { GMemoryValidationStatisticsEnabled = bEnabled; if (GExternAPI.OnMemoryValidationStatisticsChanged) { GExternAPI.OnMemoryValidationStatisticsChanged(); } } #else UE_AUTORTFM_UNUSED(bEnabled); #endif } UE_AUTORTFM_ALWAYS_OPEN void DebugBreakIfMemoryValidationFails() { #if UE_AUTORTFM // Check memory validation is enabled, otherwise we won't have a // hash to compare against. AUTORTFM_ASSERT(GetMemoryValidationLevel() == EMemoryValidationLevel::Error || GetMemoryValidationLevel() == EMemoryValidationLevel::Warn); if (FTransaction* Transaction = AutoRTFM::FContext::Get()->GetMaterializedTransaction()) { Transaction->DebugBreakIfMemoryValidationFails(); } #endif } } } #if (defined(__AUTORTFM_ENABLED) && __AUTORTFM_ENABLED) #include "AutoRTFMConstants.h" #include "CallNest.h" #include "Context.h" #include "ContextInlines.h" #include "ContextStatus.h" #include "FunctionMapInlines.h" #include "TransactionInlines.h" #include "Toggles.h" #include "Utils.h" // This is the implementation of the AutoRTFM.h API. Ideally, functions here should just delegate to some internal API. // For now, I have these functions also perform some error checking. #define AUTORTFM_CORE_API [[clang::autortfm_core_api]] namespace AutoRTFM { namespace { // Internal closed-variant implementations. AUTORTFM_CORE_API bool RTFM_autortfm_is_transactional() { return true; } AUTORTFM_CORE_API autortfm_result RTFM_autortfm_transact(void (*UninstrumentedWork)(void*), void (*InstrumentedWork)(void*), void* Arg) { FContext* Context = FContext::Get(); return static_cast(Context->Transact(UninstrumentedWork, InstrumentedWork, Arg)); } UE_AUTORTFM_FORCEINLINE autortfm_result TransactThenOpenImpl(void (*UninstrumentedWork)(void*), void* Arg, const void* ReturnAddress) { return static_cast( AutoRTFM::Transact([&] { autortfm_open(UninstrumentedWork, Arg, ReturnAddress); })); } AUTORTFM_CORE_API autortfm_result RTFM_autortfm_transact_then_open(void (*UninstrumentedWork)(void*), void (*InstrumentedWork)(void*), void* Arg, const void* ReturnAddress) { return TransactThenOpenImpl(UninstrumentedWork, Arg, ReturnAddress); } AUTORTFM_CORE_API void RTFM_autortfm_commit(void (*UninstrumentedWork)(void*), void (*InstrumentedWork)(void*), void* Arg) { autortfm_result Result = autortfm_transact(UninstrumentedWork, InstrumentedWork, Arg); AUTORTFM_FATAL_IF(Result != autortfm_committed, "Unexpected transaction result: %u", Result); } AUTORTFM_CORE_API void RTFM_autortfm_abort() { FContext* Context = FContext::Get(); Context->AbortByRequestAndThrow(); } AUTORTFM_CORE_API void RTFM_autortfm_start_transaction() { AUTORTFM_FATAL("The function `autortfm_start_transaction` was called from closed code"); } AUTORTFM_CORE_API void RTFM_autortfm_commit_transaction() { AUTORTFM_FATAL("The function `RTFM_autortfm_commit_transaction` was called from closed code"); } AUTORTFM_CORE_API void RTFM_autortfm_abort_transaction() { FContext* const Context = FContext::Get(); Context->AbortTransaction(EContextStatus::AbortedByRequest); } AUTORTFM_CORE_API void RTFM_autortfm_clear_transaction_status() { AUTORTFM_FATAL("The function `autortfm_clear_transaction_status` was called from closed code"); AutoRTFM::InternalUnreachable(); } AUTORTFM_CORE_API autortfm_status RTFM_autortfm_close(void (*UninstrumentedWork)(void*), void (*InstrumentedWork)(void*), void* Arg) { FContext* const Context = FContext::Get(); if (AUTORTFM_LIKELY(InstrumentedWork)) { InstrumentedWork(Arg); } else { AUTORTFM_REPORT_ERROR("Could not find function %p '%s' for autortfm_close()", reinterpret_cast(UninstrumentedWork), GetFunctionDescription(UninstrumentedWork).c_str()); } return static_cast(Context->GetStatus()); } extern "C" UE_AUTORTFM_NOAUTORTFM UE_AUTORTFM_API void autortfm_pre_open(autortfm_memory_validation_level MemoryValidationLevel) { if (FTransaction* Transaction = FContext::Get()->GetCurrentTransaction()) { AutoRTFM::EMemoryValidationLevel Level = MemoryValidationLevel == autortfm_memory_validation_level_default ? ForTheRuntime::GetMemoryValidationLevel() : static_cast(MemoryValidationLevel); Transaction->SetOpenActive(Level, __builtin_return_address(0)); } } extern "C" UE_AUTORTFM_NOAUTORTFM UE_AUTORTFM_API void autortfm_post_open() { if (FTransaction* Transaction = FContext::Get()->GetCurrentTransaction()) { if (Transaction->IsOpenActive()) // Transaction may have been aborted. { Transaction->SetClosedActive(); } } } extern "C" UE_AUTORTFM_NOAUTORTFM UE_AUTORTFM_API void autortfm_pre_static_local_initializer() { if (FContext* Context = FContext::Get()) { Context->EnteringStaticLocalInitializer(); } } extern "C" UE_AUTORTFM_NOAUTORTFM UE_AUTORTFM_API void autortfm_post_static_local_initializer() { if (FContext* Context = FContext::Get()) { Context->LeavingStaticLocalInitializer(); } } extern "C" AUTORTFM_CORE_API UE_AUTORTFM_API void autortfm_open_from_closed_explicit_validation(autortfm_memory_validation_level MemoryValidationLevel, void (*Work)(void*), void* Arg, const void* ReturnAddress) { FTransaction* Transaction = FContext::Get()->GetCurrentTransaction(); if (Transaction) { AutoRTFM::EMemoryValidationLevel Level = MemoryValidationLevel == autortfm_memory_validation_level_default ? ForTheRuntime::GetMemoryValidationLevel() : static_cast(MemoryValidationLevel); Transaction->SetOpenActive(Level, ReturnAddress); } Work(Arg); FContext* Context = FContext::Get(); if (Transaction == Context->GetCurrentTransaction() && Transaction->IsOpenActive()) // Transaction may have been aborted. { Transaction->SetClosedActive(); } if (Context->IsAborting()) { Context->Throw(); } } extern "C" AUTORTFM_CORE_API UE_AUTORTFM_API void autortfm_open_from_closed(void (*Work)(void*), void* Arg, const void* ReturnAddress) { FTransaction* Transaction = FContext::Get()->GetCurrentTransaction(); if (Transaction) { Transaction->SetOpenActive(ForTheRuntime::GetMemoryValidationLevel(), ReturnAddress); } Work(Arg); FContext* Context = FContext::Get(); if (Transaction == Context->GetCurrentTransaction() && Transaction->IsOpenActive()) // Transaction may have been aborted. { Transaction->SetClosedActive(); } if (Context->IsAborting()) { Context->Throw(); } } AUTORTFM_CORE_API void RTFM_autortfm_record_open_write_err(void*, size_t) { AUTORTFM_FATAL("The function `autortfm_record_open_write` was called from closed code"); } AUTORTFM_CORE_API void RTFM_CascadingAbortTransactionInternal(TTask&& RunAfterAbort) { FContext* const Context = FContext::Get(); AUTORTFM_ASSERT(Context->GetStatus() == EContextStatus::OnTrack); Context->AbortTransactionWithPostAbortCallback(EContextStatus::AbortedByCascadingAbort, std::move(RunAfterAbort)); } AUTORTFM_CORE_API void RTFM_CascadingRetryTransactionInternal(TTask && RunAfterAbortBeforeRetryWork) { FContext* Context = FContext::Get(); AUTORTFM_ASSERT(Context->GetStatus() == EContextStatus::OnTrack); Context->AbortTransactionWithPostAbortCallback(EContextStatus::AbortedByCascadingRetry, std::move(RunAfterAbortBeforeRetryWork)); } AUTORTFM_CORE_API void RTFM_OnCommitInternal(TTask && Work) { FContext* Context = FContext::Get(); AUTORTFM_ASSERT(Context->GetStatus() == EContextStatus::OnTrack); Context->GetCurrentTransaction()->DeferUntilCommit(std::move(Work)); } AUTORTFM_CORE_API void RTFM_OnAbortInternal(TTask&& Work) { FContext* Context = FContext::Get(); AUTORTFM_ASSERT(Context->GetStatus() == EContextStatus::OnTrack); Context->GetCurrentTransaction()->DeferUntilAbort(std::move(Work)); } AUTORTFM_CORE_API void RTFM_PushOnCommitHandlerInternal(const void* Key, TTask&& Work) { FContext* Context = FContext::Get(); AUTORTFM_ASSERT(Context->GetStatus() == EContextStatus::OnTrack); Context->GetCurrentTransaction()->PushDeferUntilCommitHandler(Key, std::move(Work)); } AUTORTFM_CORE_API void RTFM_PopOnCommitHandlerInternal(const void* Key) { FContext* Context = FContext::Get(); AUTORTFM_ASSERT(Context->GetStatus() == EContextStatus::OnTrack); Context->GetCurrentTransaction()->PopDeferUntilCommitHandler(Key); } AUTORTFM_CORE_API void RTFM_PopAllOnCommitHandlersInternal(const void* Key) { FContext* Context = FContext::Get(); AUTORTFM_ASSERT(Context->GetStatus() == EContextStatus::OnTrack); Context->GetCurrentTransaction()->PopAllDeferUntilCommitHandlers(Key); } AUTORTFM_CORE_API void RTFM_PushOnAbortHandlerInternal(const void* Key, TTask&& Work) { FContext* Context = FContext::Get(); AUTORTFM_ASSERT(Context->GetStatus() == EContextStatus::OnTrack); Context->GetCurrentTransaction()->PushDeferUntilAbortHandler(Key, std::move(Work)); } AUTORTFM_CORE_API void RTFM_PopOnAbortHandlerInternal(const void* Key) { FContext* Context = FContext::Get(); AUTORTFM_ASSERT(Context->GetStatus() == EContextStatus::OnTrack); Context->GetCurrentTransaction()->PopDeferUntilAbortHandler(Key); } AUTORTFM_CORE_API void RTFM_PopAllOnAbortHandlersInternal(const void* Key) { FContext* Context = FContext::Get(); AUTORTFM_ASSERT(Context->GetStatus() == EContextStatus::OnTrack); Context->GetCurrentTransaction()->PopAllDeferUntilAbortHandlers(Key); } AUTORTFM_CORE_API void RTFM_autortfm_on_commit(void (*Work)(void*), void* Arg) { RTFM_OnCommitInternal([Work, Arg] { Work(Arg); }); } AUTORTFM_CORE_API void RTFM_autortfm_on_abort(void (*Work)(void*), void* Arg) { RTFM_OnAbortInternal([Work, Arg] { Work(Arg); }); } AUTORTFM_CORE_API void RTFM_autortfm_push_on_abort_handler(const void* Key, void (*Work)(void*), void* Arg) { RTFM_PushOnAbortHandlerInternal(Key, [Work, Arg] { Work(Arg); }); } AUTORTFM_CORE_API void RTFM_autortfm_pop_on_abort_handler(const void* Key) { RTFM_PopOnAbortHandlerInternal(Key); } AUTORTFM_CORE_API void* RTFM_autortfm_did_allocate(void* Ptr, size_t Size) { FContext* Context = FContext::Get(); Context->DidAllocate(Ptr, Size); return Ptr; } AUTORTFM_CORE_API void RTFM_autortfm_did_free(void* Ptr) { // We should never-ever-ever actually free memory from within closed code of // a transaction. AutoRTFM::InternalUnreachable(); } AUTORTFM_CORE_API bool IsAutoRTFMInitialized() { return FContext::Get() != nullptr; } // Consume the GPendingOpenToClosedRegistrations linked list to register the // open -> closed functions. This is done via a linked-list to avoid heap // allocations before AutoRTFM is initialized. AUTORTFM_CORE_API void ProcessAllPendingOpenToClosedRegistrations() { AUTORTFM_ASSERT(IsAutoRTFMInitialized()); FunctionMapAdd(GPendingOpenToClosedRegistrations); GPendingOpenToClosedRegistrations = nullptr; } } // anonymous namespace // The AutoRTFM public API. extern "C" void autortfm_initialize(const autortfm_extern_api* ExternAPI) { AUTORTFM_ENSURE_MSG(!FContext::Get(), "AutoRTFM initialized twice"); AUTORTFM_ASSERT(ExternAPI); AUTORTFM_ASSERT(ExternAPI->Allocate); AUTORTFM_ASSERT(ExternAPI->AllocateZeroed); AUTORTFM_ASSERT(ExternAPI->Reallocate); AUTORTFM_ASSERT(ExternAPI->Free); GExternAPI = *ExternAPI; FContext::Create(); ProcessAllPendingOpenToClosedRegistrations(); } // Each function will be forked by the compiler into an open and closed variant. // autortfm_is_closed() is used to branch to the closed variants declared above. extern "C" bool autortfm_is_transactional() { if (autortfm_is_closed()) { return RTFM_autortfm_is_transactional(); } if (ForTheRuntime::IsAutoRTFMRuntimeEnabled()) { FContext* Context = FContext::Get(); return Context && Context->IsTransactional(); } return false; } extern "C" bool autortfm_is_committing_or_aborting() { if (ForTheRuntime::IsAutoRTFMRuntimeEnabled()) { FContext* Context = FContext::Get(); return Context && Context->IsCommittingOrAborting(); } return false; } extern "C" autortfm_result autortfm_transact(void (*UninstrumentedWork)(void*), void (*InstrumentedWork)(void*), void* Arg) { if (autortfm_is_closed()) { return RTFM_autortfm_transact(UninstrumentedWork, InstrumentedWork, Arg); } if (ForTheRuntime::IsAutoRTFMRuntimeEnabled()) { return static_cast(FContext::Get()->Transact(UninstrumentedWork, InstrumentedWork, Arg)); } (*UninstrumentedWork)(Arg); return autortfm_committed; } extern "C" autortfm_result autortfm_transact_then_open(void (*UninstrumentedWork)(void*), void (*InstrumentedWork)(void*), void* Arg) { if (autortfm_is_closed()) { return RTFM_autortfm_transact_then_open(UninstrumentedWork, InstrumentedWork, Arg, __builtin_return_address(0)); } return TransactThenOpenImpl(UninstrumentedWork, Arg, __builtin_return_address(0)); } extern "C" void autortfm_commit(void (*UninstrumentedWork)(void*), void (*InstrumentedWork)(void*), void* Arg) { if (autortfm_is_closed()) { return RTFM_autortfm_commit(UninstrumentedWork, InstrumentedWork, Arg); } autortfm_result Result = autortfm_transact(UninstrumentedWork, InstrumentedWork, Arg); AUTORTFM_FATAL_IF(Result != autortfm_committed, "Unexpected transaction result: %u", Result); } extern "C" void autortfm_abort() { if (autortfm_is_closed()) { return RTFM_autortfm_abort(); } AUTORTFM_FATAL_IF(!FContext::Get()->IsTransactional(), "The function `autortfm_abort` was called from outside a transaction"); FContext::Get()->AbortByRequestAndThrow(); } extern "C" bool autortfm_start_transaction() { if (autortfm_is_closed()) { RTFM_autortfm_start_transaction(); return false; } FContext* Context = FContext::Get(); AUTORTFM_FATAL_IF(!Context->IsTransactional(), "The function `autortfm_start_transaction` was called from outside a transact"); Context->StartTransaction(ForTheRuntime::GetMemoryValidationLevel()); return true; } extern "C" autortfm_result autortfm_commit_transaction() { if (autortfm_is_closed()) { RTFM_autortfm_commit_transaction(); return autortfm_aborted_by_language; } AUTORTFM_FATAL_IF(!FContext::Get()->IsTransactional(), "The function `autortfm_commit_transaction` was called from outside a transact"); return static_cast(FContext::Get()->CommitTransaction()); } extern "C" void autortfm_abort_transaction() { AUTORTFM_ASSERT(autortfm_is_closed()); // RollbackTransaction should be used when in the open. RTFM_autortfm_abort_transaction(); } extern "C" autortfm_result autortfm_rollback_transaction() { AUTORTFM_ASSERT(!autortfm_is_closed()); // AbortTransaction should be used when in the closed. FContext* const Context = FContext::Get(); AUTORTFM_FATAL_IF(!Context->IsTransactional(), "The function `autortfm_abort_transaction` was called from outside a transact"); return static_cast(Context->RollbackTransaction(EContextStatus::AbortedByRequest)); } extern "C" autortfm_result autortfm_cascading_rollback_transaction() { AUTORTFM_ASSERT(!autortfm_is_closed()); FContext* const Context = FContext::Get(); AUTORTFM_FATAL_IF(!Context->IsTransactional(), "The function `autortfm_cascading_rollback_transaction` was called from outside a transact"); return static_cast(Context->RollbackTransaction(EContextStatus::AbortedByCascadingAbort)); } extern "C" void autortfm_clear_transaction_status() { if (autortfm_is_closed()) { return RTFM_autortfm_clear_transaction_status(); } AUTORTFM_ASSERT(FContext::Get()->IsAborting()); FContext::Get()->ClearTransactionStatus(); } extern "C" autortfm_status autortfm_get_context_status() { return static_cast(FContext::Get()->GetStatus()); } extern "C" UE_AUTORTFM_NOAUTORTFM bool autortfm_is_aborting() { if (ForTheRuntime::IsAutoRTFMRuntimeEnabled()) { return FContext::Get()->IsAborting(); } return false; } extern "C" UE_AUTORTFM_NOAUTORTFM bool autortfm_current_nest_throw() { FContext::Get()->Throw(); return true; } extern "C" void autortfm_open_explicit_validation(autortfm_memory_validation_level ValidationLevel, void (*Work)(void*), void* Arg, const void* ReturnAddress) { if (autortfm_is_closed()) { return autortfm_open_from_closed_explicit_validation(ValidationLevel, Work, Arg, ReturnAddress); } Work(Arg); } extern "C" void autortfm_open(void (*Work)(void*), void* Arg, const void* ReturnAddress) { if (autortfm_is_closed()) { return autortfm_open_from_closed(Work, Arg, ReturnAddress); } Work(Arg); } extern "C" autortfm_status autortfm_close(void (*UninstrumentedWork)(void*), void (*InstrumentedWork)(void*), void* Arg) { if (autortfm_is_closed()) { return RTFM_autortfm_close(UninstrumentedWork, InstrumentedWork, Arg); } autortfm_status Result = autortfm_status_ontrack; if (ForTheRuntime::IsAutoRTFMRuntimeEnabled()) { AUTORTFM_FATAL_IF(!FContext::Get()->IsTransactional(), "Close called from an outside a transaction"); FContext* const Context = FContext::Get(); if (AUTORTFM_LIKELY(InstrumentedWork)) { Result = static_cast(Context->CallClosedNest(InstrumentedWork, Arg)); } else { std::string FunctionDescription = GetFunctionDescription(UninstrumentedWork); if (ForTheRuntime::GetInternalAbortAction() == ForTheRuntime::EAutoRTFMInternalAbortActionState::Crash) { AUTORTFM_FATAL("Could not find function %p '%s' in autortfm_close()", UninstrumentedWork, FunctionDescription.c_str()); } else { AUTORTFM_ENSURE_MSG(!ForTheRuntime::GetEnsureOnInternalAbort(), "Could not find function %p '%s' in autortfm_close()", UninstrumentedWork, FunctionDescription.c_str()); } Context->AbortByLanguageAndThrow(); } } else { UninstrumentedWork(Arg); } return Result; } extern "C" void autortfm_record_open_write(void* Ptr, size_t Size) { if (autortfm_is_closed()) { return RTFM_autortfm_record_open_write_err(Ptr, Size); } else if (FContext* Context = FContext::Get(); Context && Context->IsTransactional()) { if (FTransaction* CurrentTransaction = Context->GetCurrentTransaction()) { CurrentTransaction->RecordWrite(Ptr, Size); } } } extern "C" void autortfm_record_open_write_no_memory_validation(void* Ptr, size_t Size) { if (autortfm_is_closed()) { return RTFM_autortfm_record_open_write_err(Ptr, Size); } else if (FContext* Context = FContext::Get(); Context && Context->IsTransactional()) { if (FTransaction* CurrentTransaction = Context->GetCurrentTransaction()) { CurrentTransaction->RecordWrite(Ptr, Size, /* bNoMemoryValidation */ true); } } } extern "C" UE_AUTORTFM_NOAUTORTFM void autortfm_register_open_to_closed_functions(autortfm_open_to_closed_table* Table) { Table->Next = GPendingOpenToClosedRegistrations; GPendingOpenToClosedRegistrations = Table; if (IsAutoRTFMInitialized()) { ProcessAllPendingOpenToClosedRegistrations(); } } extern "C" UE_AUTORTFM_NOAUTORTFM void autortfm_unregister_open_to_closed_functions(autortfm_open_to_closed_table* Table) { if (Table == GPendingOpenToClosedRegistrations) { GPendingOpenToClosedRegistrations = Table->Next; } if (Table->Next) { Table->Next->Prev = Table->Prev; } if (Table->Prev) { Table->Prev->Next = Table->Next; } Table->Prev = nullptr; Table->Next = nullptr; // Note: If AutoRTFM is already initialized, we currently do *not* remove // the registered functions from the function map. The reason for this is // that we can register the same open address multiple times, where the // closed address uses the value of the last register call. // To support unregistering these cleanly, we'd need to increase the // complexity of the function map - either by storing a list of all the // closed functions that were registered for an open, or entirely rebuilding // the map from the autortfm_open_to_closed_table lists. So far, keeping // stale mappings has not been an issue, but if it does become an issue, // then something will need to be done here. } extern "C" bool autortfm_is_on_current_transaction_stack(void* Ptr) { if (FContext* Context = FContext::Get()) { if (!Context->IsTransactional()) { return false; } if (FTransaction* CurrentTransaction = Context->GetCurrentTransaction()) { return CurrentTransaction->IsOnStack(Ptr); } } return false; } void ForTheRuntime::Initialize(const ForTheRuntime::FExternAPI& ExternAPI) { autortfm_initialize(&ExternAPI); } void ForTheRuntime::CascadingAbortTransactionInternal(TTask&& RunAfterAbort) { AUTORTFM_ASSERT(autortfm_is_closed()); RTFM_CascadingAbortTransactionInternal(std::move(RunAfterAbort)); } void ForTheRuntime::CascadingRetryTransactionInternal(TTask&& RunAfterAbortBeforeRetryWork) { if (autortfm_is_closed()) { return RTFM_CascadingRetryTransactionInternal(std::move(RunAfterAbortBeforeRetryWork)); } } void ForTheRuntime::OnCommitInternal(TTask && Work) { if (autortfm_is_closed()) { return RTFM_OnCommitInternal(std::move(Work)); } Work(); } void ForTheRuntime::OnAbortInternal(TTask && Work) { if (autortfm_is_closed()) { return RTFM_OnAbortInternal(std::move(Work)); } } void ForTheRuntime::PushOnCommitHandlerInternal(const void* Key, TTask&& Work) { if (autortfm_is_closed()) { return RTFM_PushOnCommitHandlerInternal(Key, std::move(Work)); } } void ForTheRuntime::PopOnCommitHandlerInternal(const void* Key) { if (autortfm_is_closed()) { return RTFM_PopOnCommitHandlerInternal(Key); } } void ForTheRuntime::PopAllOnCommitHandlersInternal(const void* Key) { if (autortfm_is_closed()) { return RTFM_PopAllOnCommitHandlersInternal(Key); } } void ForTheRuntime::PushOnAbortHandlerInternal(const void* Key, TTask && Work) { if (autortfm_is_closed()) { return RTFM_PushOnAbortHandlerInternal(Key, std::move(Work)); } } void ForTheRuntime::PopOnAbortHandlerInternal(const void* Key) { if (autortfm_is_closed()) { return RTFM_PopOnAbortHandlerInternal(Key); } } void ForTheRuntime::PopAllOnAbortHandlersInternal(const void* Key) { if (autortfm_is_closed()) { return RTFM_PopAllOnAbortHandlersInternal(Key); } } extern "C" void autortfm_on_commit(void (*Work)(void*), void* Arg) { if (autortfm_is_closed()) { return RTFM_autortfm_on_commit(Work, Arg); } Work(Arg); } extern "C" void autortfm_on_abort(void (*Work)(void*), void* Arg) { if (autortfm_is_closed()) { return RTFM_autortfm_on_abort(Work, Arg); } } extern "C" void autortfm_push_on_abort_handler(const void* Key, void (*Work)(void*), void* Arg) { if (autortfm_is_closed()) { return RTFM_autortfm_push_on_abort_handler(Key, Work, Arg); } } extern "C" void autortfm_pop_on_abort_handler(const void* Key) { if (autortfm_is_closed()) { return RTFM_autortfm_pop_on_abort_handler(Key); } } extern "C" void* autortfm_did_allocate(void* Ptr, size_t Size) { if (autortfm_is_closed()) { return RTFM_autortfm_did_allocate(Ptr, Size); } return Ptr; } extern "C" void autortfm_did_free(void* Ptr) { if (autortfm_is_closed()) { return RTFM_autortfm_did_free(Ptr); } // We only need to process did free if we need to track allocation locations. if constexpr (bTrackAllocationLocations) { if (FContext* Context = FContext::Get(); Context && Context->IsTransactional()) { // We only care about frees that are occurring when the transaction // is in an on-going state (it's not committing or aborting). if (EContextStatus::OnTrack == Context->GetStatus()) { Context->DidFree(Ptr); } } } } // 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. extern "C" UE_AUTORTFM_NOAUTORTFM void autortfm_check_abi(void* const Ptr, const size_t Size) { struct FConstants final { const uint32_t Major = AutoRTFM::Constants::Major; const uint32_t Minor = AutoRTFM::Constants::Minor; const uint32_t Patch = AutoRTFM::Constants::Patch; // This is messy - but we want to do comparisons but without comparing any padding bytes. // Before C++20 we cannot use a default created operator== and operator!=, so we use this // ugly trick to just compare the members. private: auto Tied() const { return std::make_tuple(Major, Minor, Patch); } public: bool operator==(const FConstants& Other) const { return Tied() == Other.Tied(); } bool operator!=(const FConstants& Other) const { return !(*this == Other); } } RuntimeConstants; AUTORTFM_FATAL_IF(sizeof(FConstants) != Size, "ABI error between AutoRTFM compiler and runtime"); const FConstants* const CompilerConstants = static_cast(Ptr); AUTORTFM_FATAL_IF(RuntimeConstants != *CompilerConstants, "ABI error between AutoRTFM compiler and runtime"); } } // namespace AutoRTFM #endif // defined(__AUTORTFM) && __AUTORTFM