Files
UnrealEngine/Engine/Source/Runtime/AutoRTFM/Private/Transaction.h
2025-05-18 13:04:45 +08:00

237 lines
9.2 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#if (defined(__AUTORTFM) && __AUTORTFM)
#include "AutoRTFM.h"
#include "HitSet.h"
#include "IntervalTree.h"
#include "IntrusivePool.h"
#include "Stack.h"
#include "StackRange.h"
#include "Stats.h"
#include "TaskArray.h"
#include "WriteLog.h"
namespace AutoRTFM
{
class FContext;
class FTransaction final
{
friend class TIntrusivePool<FTransaction, 16>;
void Resurrect(FContext* const InContext);
void Suppress();
FTransaction** GetIntrusiveAddress();
// Constructor.
// The transaction is constructed in an initial Closed state.
FTransaction(FContext* const Context);
// Destructor.
// The transaction must we in a Done state.
~FTransaction();
public:
// State flow diagram for a transaction:
// ┌──────────────────────────────────────┐
// │ Uninitialized │
// └──────────────────────────────────────┘
// │ │
// ▼ ▼
// ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐
// │ OpenInactive │ ←→ │ OpenActive │ ←→ │ ClosedActive │ ←→ │ ClosedInactive │
// └────────────────┘ └────────────────┘ └────────────────┘ └────────────────┘
// │ │
// ▼ ▼
// ┌──────────────────────────────────────┐
// │ Done │
// └──────────────────────────────────────┘
enum class EState
{
// The initial state for the transaction.
// Can only transition to OpenActive or ClosedActive.
Uninitialized,
// The transaction is open (not recording writes) and is the current transaction.
// Can only transition to OpenInactive, ClosedActive or Done.
OpenActive,
// The transaction is closed (recording writes) and is the current transaction.
// Can only transition to ClosedInactive, OpenActive or Done.
ClosedActive,
// The transaction is open and the current transaction is a descendant.
// Can only transition to OpenActive.
OpenInactive,
// The transaction is closed and the current transaction is a descendant.
// Can only transition to ClosedActive.
ClosedInactive,
// The transaction is committed or aborted.
// Once in this state the transaction must be reset with Reset() or destructed.
Done,
};
void Initialize(FTransaction* Parent, bool bIsScoped, FStackRange StackRange);
// Clears the tracked transaction state and resets back to the default Uninitialized state.
void Reset();
bool IsNested() const { return !!Parent; }
FTransaction* GetParent() const { return Parent; }
// This should just use type displays or ranges. Maybe ranges could even work out great.
bool IsNestedWithin(const FTransaction* Other) const
{
for (const FTransaction* Current = this; ; Current = Current->Parent)
{
if (!Current)
{
return false;
}
else if (Current == Other)
{
return true;
}
}
}
inline bool IsScopedTransaction() const { return bIsStackScoped; }
void DeferUntilCommit(TTask<void()>&&);
void DeferUntilAbort(TTask<void()>&&);
void PushDeferUntilCommitHandler(const void* Key, TTask<void()>&&);
void PopDeferUntilCommitHandler(const void* Key);
void PopAllDeferUntilCommitHandlers(const void* Key);
void PushDeferUntilAbortHandler(const void* Key, TTask<void()>&&);
void PopDeferUntilAbortHandler(const void* Key);
void PopAllDeferUntilAbortHandlers(const void* Key);
[[noreturn]] void AbortAndThrow();
void AbortWithoutThrowing();
bool AttemptToCommit();
// Record that a write is about to occur at the given LogicalAddress of Size bytes.
void RecordWrite(void* LogicalAddress, size_t Size, bool bNoMemoryValidation = false);
template<unsigned SIZE> void RecordWrite(void* LogicalAddress);
template<unsigned SIZE> void RecordWriteInsertedSlow(void* LogicalAddress);
template<unsigned SIZE> void RecordWriteNotInsertedSlow(void* LogicalAddress);
void DidAllocate(void* LogicalAddress, size_t Size);
void DidFree(void* LogicalAddress);
void SetOpenActive(EMemoryValidationLevel MemoryValidationLevel, const void* ReturnAddress);
void SetClosedActive();
void SetOpenInactive();
void SetClosedInactive();
void SetActive();
void SetInactive();
void SetDone();
// A debug helper that will break to the debugger if the memory validation
// hash no longer matches. Useful for isolating where the open write happened.
void DebugBreakIfMemoryValidationFails();
// State querying
EState State() const { return CurrentState; }
bool IsFresh() const;
bool IsOpenActive() const { return CurrentState == EState::OpenActive; }
bool IsClosedActive() const { return CurrentState == EState::ClosedActive; }
bool IsOpenInactive() const { return CurrentState == EState::OpenInactive; }
bool IsClosedInactive() const { return CurrentState == EState::ClosedInactive; }
bool IsOpen() const { return IsOpenActive() || IsOpenInactive(); }
bool IsClosed() const { return IsClosedActive() || IsClosedInactive(); }
bool IsActive() const { return IsOpenActive() || IsClosedActive(); }
bool IsInactive() const { return IsOpenInactive() || IsClosedInactive(); }
bool IsDone() const { return CurrentState == EState::Done; }
EMemoryValidationLevel MemoryValidationLevel() const { return CurrentMemoryValidationLevel; }
const void* OpenReturnAddress() const { return CurrentOpenReturnAddress; }
// The stack range represents all stack memory inside the transaction scope
inline FStackRange GetStackRange() const { return StackRange; }
// Returns true if the LogicalAddress is within the stack of the transaction.
inline bool IsOnStack(const void* LogicalAddress) const;
private:
using FWriteHash = FWriteLog::FHash;
using FTaskArray = TTaskArray<TTask<void()>>;
void Undo();
void CommitNested();
bool AttemptToCommitOuterNest();
// Calculates the hash of all the memory written during the transaction and stores it to
// RecordedWriteHash.
void RecordWriteHash();
// Compares the hash of all the memory that will be written on abort against the
// RecordedWriteHash, raising a warning or error if the hashes are not equal.
// See CalculateNestedWriteHash().
void ValidateWriteHash() const;
// Returns a hash of all the memory written during this transaction and all parent transactions.
// Can be used to ensure that no transaction written memory is modified during an open.
FWriteHash CalculateNestedWriteHash() const;
// Returns a hash of all the memory written during this transaction and all parent transactions.
// Can be used to ensure that no transaction written memory is modified during an open.
// NumWriteEntries is the number of write entries to hash before stopping.
// OpenReturnAddress is the return address for the open that triggered this hash.
FWriteHash CalculateNestedWriteHashWithLimit(size_t NumWriteEntries, const void *OpenReturnAddress) const;
void CollectStats() const;
bool ShouldRecordWrite(void* LogicalAddress) const;
void SetOpenActiveValidatorEnabled(EMemoryValidationLevel MemoryValidationLevel, const void* ReturnAddress);
template<EState NewState>
void SetState();
FContext* Context;
// Are we nested? Then this is the parent.
FTransaction* Parent;
// Commit tasks run on commit in forward order.
FTaskArray CommitTasks;
// Abort tasks run on abort in reverse order.
FTaskArray AbortTasks;
// If a call to `PopOnCommitHandler` could not find a commit to pop, it is deferred
// and tried again on the parent transaction.
TStack<const void*, 8> DeferredPopOnCommitHandlers;
// If a call to `PopOnAbortHandler` could not find an abort to pop, it is deferred
// and tried again on the parent transaction.
TStack<const void*, 8> DeferredPopOnAbortHandlers;
// If a call to `PopAllOnCommitHandlers` was used and our transaction successfully
// commits, we need to propagate this to the parent too.
TStack<const void*, 1> DeferredPopAllOnCommitHandlers;
// If a call to `PopAllOnAbortHandlers` was used and our transaction successfully
// commits, we need to propagate this to the parent too.
TStack<const void*, 1> DeferredPopAllOnAbortHandlers;
FHitSet HitSet;
FIntervalTree NewMemoryTracker;
FWriteLog WriteLog;
TStatStorage<uint64_t> StatDepth = 1;
FWriteHash RecordedWriteHash;
size_t NumWriteLogsHashed;
EMemoryValidationLevel CurrentMemoryValidationLevel;
const void* CurrentOpenReturnAddress;
EState CurrentState;
FStackRange StackRange;
bool bIsStackScoped;
bool bIsInAllocateFn;
};
} // namespace AutoRTFM
#endif // (defined(__AUTORTFM) && __AUTORTFM)