UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
TransactionInlines.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#if (defined(__AUTORTFM) && __AUTORTFM)
6
7#include "Context.h"
8#include "HitSet.h"
9#include "ScopedGuard.h"
10#include "Transaction.h"
11#include "Utils.h"
12#include "WriteLog.h"
13
14#include <utility>
15
16#if __has_include(<sanitizer/asan_interface.h>)
17# include <sanitizer/asan_interface.h>
18# if defined(__SANITIZE_ADDRESS__)
19# define AUTORTFM_CHECK_ASAN_FAKE_STACKS 1
20# elif defined(__has_feature)
21# if __has_feature(address_sanitizer)
22# define AUTORTFM_CHECK_ASAN_FAKE_STACKS 1
23# endif
24# endif
25#endif
26
27#ifndef AUTORTFM_CHECK_ASAN_FAKE_STACKS
28#define AUTORTFM_CHECK_ASAN_FAKE_STACKS 0
29#endif
30
31namespace AutoRTFM
32{
33
34UE_AUTORTFM_FORCEINLINE bool FTransaction::IsOnStack(const void* LogicalAddress) const
35{
36 if (StackRange.Contains(LogicalAddress))
37 {
38 return true;
39 }
40
41#if AUTORTFM_CHECK_ASAN_FAKE_STACKS
43 {
44 void* Beg = nullptr;
45 void* End = nullptr;
46 void* RealAddress = __asan_addr_is_in_fake_stack(FakeStack, const_cast<void*>(LogicalAddress), &Beg, &End);
47 return RealAddress && StackRange.Contains(RealAddress);
48 }
49#endif // AUTORTFM_CHECK_ASAN_FAKE_STACKS
50
51 return false;
52}
53
54UE_AUTORTFM_FORCEINLINE bool FTransaction::ShouldRecordWrite(void* LogicalAddress) const
55{
56 // We cannot record writes to stack memory used within the transaction, as
57 // undoing the writes may corrupt stack memory that has been unwound or
58 // is now being used for a different variable from the one the write was
59 // made.
61}
62
64void FTransaction::RecordWrite(void* LogicalAddress, size_t Size, bool bNoMemoryValidation /* = false */)
65{
66 if (AUTORTFM_UNLIKELY(0 == Size))
67 {
68 return;
69 }
70
72 {
73 Stats.Collect<EStatsKind::HitSetSkippedBecauseOfStackLocalMemory>();
74 return;
75 }
76
77 if (Size <= FHitSet::MaxSize)
78 {
80 HitSetEntry.Address = reinterpret_cast<uintptr_t>(LogicalAddress);
81 HitSetEntry.Size = static_cast<uint16_t>(Size);
82 HitSetEntry.bNoMemoryValidation = bNoMemoryValidation;
83
84 if (HitSet.FindOrTryInsert(HitSetEntry))
85 {
86 Stats.Collect<EStatsKind::HitSetHit>();
87 return;
88 }
89
90 Stats.Collect<EStatsKind::HitSetMiss>();
91 }
92
94 {
95 Stats.Collect<EStatsKind::NewMemoryTrackerHit>();
96 return;
97 }
98
99 Stats.Collect<EStatsKind::NewMemoryTrackerMiss>();
100
102 {
103 .LogicalAddress = static_cast<std::byte*>(LogicalAddress),
104 .Data = static_cast<std::byte*>(LogicalAddress),
105 .Size = Size,
106 .bNoMemoryValidation = bNoMemoryValidation
107 });
108}
109
110template<unsigned SIZE> AUTORTFM_NO_ASAN UE_AUTORTFM_FORCEINLINE void FTransaction::RecordWrite(void* LogicalAddress)
111{
112 static_assert(SIZE <= 8);
113
115 {
116 Stats.Collect<EStatsKind::HitSetSkippedBecauseOfStackLocalMemory>();
117 return;
118 }
119
120 FHitSetEntry Entry{};
121 Entry.Address = reinterpret_cast<uintptr_t>(LogicalAddress);
122 Entry.Size = static_cast<uint16_t>(SIZE);
123
124 switch (HitSet.FindOrTryInsertNoResize(Entry))
125 {
126 case FHitSet::EInsertResult::Exists:
127 Stats.Collect<EStatsKind::HitSetHit>();
128 return;
129 case FHitSet::EInsertResult::Inserted:
130 AUTORTFM_MUST_TAIL return FTransaction::RecordWriteInsertedSlow<SIZE>(LogicalAddress);
131 case FHitSet::EInsertResult::NotInserted:
132 AUTORTFM_MUST_TAIL return FTransaction::RecordWriteNotInsertedSlow<SIZE>(LogicalAddress);
133 }
134}
135
136template<unsigned SIZE> AUTORTFM_NO_ASAN UE_AUTORTFM_FORCENOINLINE void FTransaction::RecordWriteNotInsertedSlow(void* LogicalAddress)
137{
138 FHitSetEntry Entry{};
139 Entry.Address = reinterpret_cast<uintptr_t>(LogicalAddress);
140 Entry.Size = static_cast<uint16_t>(SIZE);
141
142 if (HitSet.FindOrTryInsert(Entry))
143 {
144 Stats.Collect<EStatsKind::HitSetHit>();
145 return;
146 }
147
148 Stats.Collect<EStatsKind::HitSetMiss>();
149
151}
152
153template<unsigned SIZE> AUTORTFM_NO_ASAN UE_AUTORTFM_FORCENOINLINE void FTransaction::RecordWriteInsertedSlow(void* LogicalAddress)
154{
155 if (NewMemoryTracker.Contains(LogicalAddress, SIZE))
156 {
157 Stats.Collect<EStatsKind::NewMemoryTrackerHit>();
158 return;
159 }
160
161 Stats.Collect<EStatsKind::NewMemoryTrackerMiss>();
162
163 WriteLog.PushSmall<SIZE>(static_cast<std::byte*>(LogicalAddress));
164}
165
166UE_AUTORTFM_FORCEINLINE void FTransaction::DidAllocate(void* LogicalAddress, const size_t Size)
167{
168 if (0 == Size || bIsInAllocateFn)
169 {
170 return;
171 }
172
173 AutoRTFM::TScopedGuard<bool> RecursionGuard(bIsInAllocateFn, true);
174 const bool DidInsert = NewMemoryTracker.Insert(LogicalAddress, Size);
176}
177
178UE_AUTORTFM_FORCEINLINE void FTransaction::DidFree(void* LogicalAddress)
179{
180 AUTORTFM_ASSERT(bTrackAllocationLocations);
181
182 // Checking if one byte is in the interval map is enough to ascertain if it
183 // is new memory and we should be worried.
184 if (!bIsInAllocateFn)
185 {
186 AutoRTFM::TScopedGuard<bool> RecursionGuard(bIsInAllocateFn, true);
188 }
189}
190
191UE_AUTORTFM_FORCEINLINE void FTransaction::DeferUntilCommit(TTask<void()>&& Callback)
192{
193 // We explicitly must copy the function here because the original was allocated
194 // within a transactional context, and thus the memory is allocating under
195 // transactionalized conditions. By copying, we create an open copy of the callback.
196 TTask<void()> Copy(Callback);
197 CommitTasks.Add(std::move(Copy));
198}
199
200UE_AUTORTFM_FORCEINLINE void FTransaction::DeferUntilPreAbort(TTask<void()>&& Callback)
201{
202 // We explicitly must copy the function here because the original was allocated
203 // within a transactional context, and thus the memory is allocating under
204 // transactionalized conditions. By copying, we create an open copy of the callback.
205 TTask<void()> Copy(Callback);
206 PreAbortTasks.Add(std::move(Copy));
207}
208
209UE_AUTORTFM_FORCEINLINE void FTransaction::DeferUntilAbort(TTask<void()>&& Callback)
210{
211 // We explicitly must copy the function here because the original was allocated
212 // within a transactional context, and thus the memory is allocating under
213 // transactionalized conditions. By copying, we create an open copy of the callback.
214 TTask<void()> Copy(Callback);
215 AbortTasks.Add(std::move(Copy));
216}
217
218UE_AUTORTFM_FORCEINLINE void FTransaction::DeferUntilComplete(TTask<void()>&& Callback)
219{
220 // Completion tasks are always stored directly on the root level of a transaction.
221 FTransaction* Transaction = this;
222 while (FTransaction* Above = Transaction->Parent)
223 {
224 Transaction = Above;
225 }
226
227 // We explicitly must copy the function here because the original was allocated
228 // within a transactional context, and thus the memory is allocating under
229 // transactionalized conditions. By copying, we create an open copy of the callback.
230 TTask<void()> Copy(Callback);
231 Transaction->CompletionTasks->Add(std::move(Copy));
232}
233
234UE_AUTORTFM_FORCEINLINE void FTransaction::PushDeferUntilCommitHandler(const void* Key, TTask<void()>&& Callback)
235{
236 // We explicitly must copy the function here because the original was allocated
237 // within a transactional context, and thus the memory was allocated under
238 // transactionalized conditions. By copying, we create an open copy of the callback.
239 TTask<void()> Copy(Callback);
240 CommitTasks.AddKeyed(Key, std::move(Copy));
241}
242
243UE_AUTORTFM_FORCEINLINE void FTransaction::PopDeferUntilCommitHandler(const void* Key)
244{
245 if (AUTORTFM_LIKELY(CommitTasks.DeleteKey(Key)))
246 {
247 return;
248 }
249
251}
252
253UE_AUTORTFM_FORCEINLINE void FTransaction::PopAllDeferUntilCommitHandlers(const void* Key)
254{
255 CommitTasks.DeleteAllMatchingKeys(Key);
256
257 // We also need to remember to run this on our parent's nest if our transaction commits.
259}
260
261UE_AUTORTFM_FORCEINLINE void FTransaction::PushDeferUntilAbortHandler(const void* Key, TTask<void()>&& Callback)
262{
263 // We explicitly must copy the function here because the original was allocated
264 // within a transactional context, and thus the memory is allocating under
265 // transactionalized conditions. By copying, we create an open copy of the callback.
266 TTask<void()> Copy(Callback);
267 AbortTasks.AddKeyed(Key, std::move(Copy));
268}
269
270UE_AUTORTFM_FORCEINLINE void FTransaction::PopDeferUntilAbortHandler(const void* Key)
271{
272 if (AUTORTFM_LIKELY(AbortTasks.DeleteKey(Key)))
273 {
274 return;
275 }
276
278}
279
280UE_AUTORTFM_FORCEINLINE void FTransaction::PopAllDeferUntilAbortHandlers(const void* Key)
281{
282 AbortTasks.DeleteAllMatchingKeys(Key);
283
284 // We also need to remember to run this on our parent's nest if our transaction commits.
286}
287
288UE_AUTORTFM_FORCEINLINE void FTransaction::CollectStats() const
289{
290 Stats.Collect<EStatsKind::AverageWriteLogEntries>(WriteLog.Num());
291 Stats.Collect<EStatsKind::MaximumWriteLogEntries>(WriteLog.Num());
292
293 Stats.Collect<EStatsKind::AverageWriteLogBytes>(WriteLog.TotalSize());
294 Stats.Collect<EStatsKind::MaximumWriteLogBytes>(WriteLog.TotalSize());
295
296 Stats.Collect<EStatsKind::AverageCommitTasks>(CommitTasks.Num());
297 Stats.Collect<EStatsKind::MaximumCommitTasks>(CommitTasks.Num());
298
299 Stats.Collect<EStatsKind::AveragePreAbortTasks>(PreAbortTasks.Num());
300 Stats.Collect<EStatsKind::MaximumPreAbortTasks>(PreAbortTasks.Num());
301
302 Stats.Collect<EStatsKind::AverageAbortTasks>(AbortTasks.Num());
303 Stats.Collect<EStatsKind::MaximumAbortTasks>(AbortTasks.Num());
304
305 Stats.Collect<EStatsKind::AverageHitSetSize>(HitSet.GetCount());
306 Stats.Collect<EStatsKind::AverageHitSetCapacity>(HitSet.GetCapacity());
307}
308
309} // namespace AutoRTFM
310
311#undef AUTORTFM_CHECK_ASAN_FAKE_STACKS
312
313#endif // (defined(__AUTORTFM) && __AUTORTFM)
OODEFFUNC typedef void(OODLE_CALLBACK t_fp_OodleCore_Plugin_Free)(void *ptr)
#define UE_AUTORTFM_FORCENOINLINE
Definition AutoRTFMDefines.h:173
#define UE_AUTORTFM_FORCEINLINE
Definition AutoRTFMDefines.h:171
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
uint32 Size
Definition VulkanMemory.cpp:4034
Definition API.cpp:57