UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
VVMTransaction.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#if WITH_VERSE_VM || defined(__INTELLISENSE__)
6
7#include "AutoRTFM.h"
8#include "Containers/Array.h"
10#include "VVMCell.h"
11#include "VVMContext.h"
12#include "VVMLog.h"
13#include "VVMPlaceholder.h"
14#include "VVMPtrVariant.h"
15#include "VVMVerse.h"
16#include "VVMWriteBarrier.h"
17
18namespace Verse
19{
20template <typename T>
21struct FWriteLog
22{
23 FWriteLog(const FWriteLog&) = delete;
24
25 FWriteLog(FWriteLog&&) = delete;
26
27 FWriteLog& operator=(const FWriteLog&) = delete;
28
29 FWriteLog& operator=(FWriteLog&&) = delete;
30
31 ~FWriteLog() = default;
32
33 FWriteLog()
34 {
35 checkSlow(IsInline());
36 std::memset(Table, 0, InitialCapacity * sizeof(uint64));
37 }
38
39 void Append(FAllocationContext Context, FWriteLog& Child)
40 {
41 for (uint32 I = 0, Last = Child.Num; I != Last; ++I)
42 {
43 AddImpl(Context, Child.Log[I]);
44 }
45 }
46
48 {
49 for (uint32 I = 0, Last = Num; I != Last; ++I)
50 {
51 Log[I].Backtrack(Context);
52 }
53 }
54
55 void Empty()
56 {
57 if (IsInline())
58 {
60 }
61 else
62 {
64 }
65 }
66
67protected:
68 void AddImpl(FAllocationContext Context, T Entry)
69 {
70 checkSlow(Entry.Key() != 0);
71 if (IsInline())
72 {
74 }
75 else
76 {
77 AddToHashTable(Context, Entry);
78 }
79 }
80
81private:
82 V_FORCEINLINE bool IsInline() { return Table == InlineTable; }
83
85 {
86 return 2 * Num > TableCapacity;
87 }
88
89 static uint64& FindBucket(uint64 Entry, uint64* Table, uint32 Capacity, bool& bIsNewEntry)
90 {
91 checkSlow(Capacity && (Capacity & (Capacity - 1)) == 0);
92 // We use a simple linear probing hash table.
93 uint32 Mask = Capacity - 1;
94 uint32 Index = ::GetTypeHash(Entry) & Mask;
95 for (;;)
96 {
97 if (!Table[Index] || Table[Index] == Entry)
98 {
100 return Table[Index];
101 }
102 Index = (Index + 1) & Mask;
103 }
104 }
105
106 FORCENOINLINE void GrowTable(FAllocationContext Context)
107 {
109 if (TableCapacity == InitialCapacity)
110 {
111 NewCapacity *= 2;
112 }
113
114 std::size_t AllocationSize = sizeof(uint64) * NewCapacity;
115 uint64* NewTable = BitCast<uint64*>(Context.AllocateAuxCell(AllocationSize));
116 std::memset(NewTable, 0, AllocationSize);
117
118 for (uint32 I = 0, Last = Num; I != Last; ++I)
119 {
120 bool bIsNewEntry;
121 FindBucket(Log[I].Key(), NewTable, NewCapacity, bIsNewEntry) = Log[I].Key();
122 }
123
125 Table = NewTable;
126 }
127
128 void AddToInlineHashTable(FAllocationContext Context, T Entry)
129 {
130 for (uint32 I = 0, Last = InitialCapacity; I != Last; ++I)
131 {
132 if (!Table[I])
133 {
134 Table[I] = Entry.Key();
135 AppendToLog(Context, Entry);
136 return;
137 }
138 if (Entry.Key() == Table[I])
139 {
140 return;
141 }
142 }
143
145 AddToHashTable(Context, Entry);
146 }
147
148 void AddToHashTable(FAllocationContext Context, T Entry)
149 {
150 bool bIsNewEntry;
151 uint64& Bucket = FindBucket(Entry.Key(), Table, TableCapacity, bIsNewEntry);
152 if (bIsNewEntry)
153 {
154 Bucket = Entry.Key();
155 AppendToLog(Context, Entry);
156 if (ShouldGrowTable())
157 {
159 }
160 }
161 }
162
164 {
165 std::memset(Table, 0, InitialCapacity * sizeof(uint64));
166 Num = 0;
167 TableCapacity = InitialCapacity;
168 LogCapacity = InitialCapacity;
169 }
170
171 void EmptyHashTable()
172 {
174 Log = BitCast<T*>(static_cast<char*>(InlineLog));
176 }
177
178 void AppendToLog(FAllocationContext Context, T Entry)
179 {
180 if (Num == LogCapacity)
181 {
183 T* NewLog = BitCast<T*>(Context.AllocateAuxCell(NewCapacity * sizeof(T)));
184 std::memcpy(NewLog, Log, Num * sizeof(T));
186 Log = NewLog;
187 }
188
189 new (Log + Num) T{::MoveTemp(Entry)};
190 ++Num;
191 }
192
193private:
194 static constexpr uint32 InitialCapacity = 4;
195
197 T* Log = BitCast<T*>(static_cast<char*>(InlineLog));
198
199 uint64 InlineTable[InitialCapacity];
200 alignas(alignof(T)) char InlineLog[InitialCapacity * sizeof(T)];
201
202 uint32 Num = 0;
203 uint32 TableCapacity = InitialCapacity;
204 // TODO: It's conceivable we could make LogCapacity a function of TableCapacity.
205 // But we're just doing the simple thing for now.
206 uint32 LogCapacity = InitialCapacity;
207};
208
209struct FTrailLogEntry
210{
211 explicit FTrailLogEntry(VPlaceholder::EMode& Mode)
212 : Ptr{&Mode}
214 , bMode{true}
215 {
216 }
217
219 : Ptr{&InValue}
221 , bMode{false}
222 {
223 }
224
225 uint64 Key() const
226 {
227 return reinterpret_cast<uint64>(Ptr);
228 }
229
231 {
232 if (bMode)
233 {
234 *static_cast<EMode*>(Ptr) = static_cast<EMode>(Value);
235 }
236 else
237 {
238 static_cast<TWriteBarrier<VValue>*>(Ptr)->Set(Context, VValue::Decode(Value));
239 }
240 }
241
242private:
243 using EMode = VPlaceholder::EMode;
244
245 void* Ptr; // VPlaceholder::Mode() is byte-aligned, leaving no bits for TPtrVariant.
247 bool bMode;
248};
249
250struct FTrailLog : FWriteLog<FTrailLogEntry>
251{
252 void Add(FAllocationContext Context, VPlaceholder::EMode& Mode)
253 {
254 AddImpl(Context, FTrailLogEntry{Mode});
255 }
256
257 void Add(FAllocationContext Context, TWriteBarrier<VValue>& Value)
258 {
259 AddImpl(Context, FTrailLogEntry{Value});
260 }
261};
262
263struct FTrail
264{
265 FTrail* GetParent() const
266 {
267 return Parent;
268 }
269
270 void Enter(FAllocationContext Context)
271 {
272 FTrail* CurrentTrail = Context.CurrentTrail();
273 V_DIE_IF(CurrentTrail == this);
276 Context.SetCurrentTrail(this);
277 }
278
279 void Exit(FAllocationContext Context)
280 {
281 FTrail* CurrentTrail = Context.CurrentTrail();
282 V_DIE_UNLESS(CurrentTrail == this);
283 Context.SetCurrentTrail(Parent);
284 if (Parent)
285 {
286 Parent->Log.Append(Context, Log);
287 }
288 Log.Empty();
289 Parent = nullptr;
290 }
291
292 void Abort(FAllocationContext Context)
293 {
294 FTrail* Next;
295 for (FTrail* I = Context.CurrentTrail();; I = Next)
296 {
297 V_DIE_UNLESS(I);
298 I->Log.Backtrack(Context);
299 I->Log.Empty();
300 Next = I->Parent;
301 I->Parent = nullptr;
302 if (I == this)
303 {
304 break;
305 }
306 }
307 Context.SetCurrentTrail(Next);
308 }
309
310 void LogBeforeWrite(FAllocationContext Context, VPlaceholder::EMode& Mode)
311 {
312 Log.Add(Context, Mode);
313 }
314
315 void LogBeforeWrite(FAllocationContext Context, TWriteBarrier<VValue>& Value)
316 {
317 Log.Add(Context, Value);
318 }
319
320private:
322 FTrail* Parent{nullptr};
323};
324
326{
327 uintptr_t Key() { return Slot.RawPtr(); }
328
330
331 FSlot Slot; // The memory location we write OldValue into on abort.
332 uint64 OldValue; // VValue or TAux<void> depending on how Slot is encoded.
333 static_assert(sizeof(OldValue) == sizeof(VValue));
334 static_assert(sizeof(OldValue) == sizeof(VCell*));
335 static_assert(sizeof(OldValue) == sizeof(TAux<void>));
336
338 : Slot(&InSlot)
339 , OldValue(OldValue.GetEncodedBits())
340 {
341 }
342
343 template <typename T, typename = std::enable_if_t<std::is_convertible_v<T*, VCell*>>>
346 , OldValue(BitCast<uint64>(OldValue))
347 {
348 }
349
351 : Slot(&InSlot)
352 , OldValue(BitCast<uint64>(OldValue.GetPtr()))
353 {
354 }
355
357 {
358 if (Slot.Is<TWriteBarrier<VValue>*>())
359 {
361 ValueSlot->Set(Context, VValue::Decode(OldValue));
362 }
363 else if (Slot.Is<TWriteBarrier<TAux<void>>*>())
364 {
366 AuxSlot->Set(Context, TAux<void>(BitCast<void*>(OldValue)));
367 }
368 else
369 {
371 ValueSlot->Set(Context, BitCast<VCell*>(OldValue));
372 }
373 }
374};
375
376struct FTransactionLog : FWriteLog<FTransactionLogEntry>
377{
378 template <typename T>
379 void Add(FAllocationContext Context, TWriteBarrier<T>& Slot)
380 {
381 AddImpl(Context, FTransactionLogEntry{Slot, Slot.Get()});
382 }
383};
384
385#if UE_BUILD_DEBUG
386#define VERSE_TRANSACTION_HAS_AUTORTFM_ID 1
387#else
388#define VERSE_TRANSACTION_HAS_AUTORTFM_ID 0
389#endif
390
391struct FTransaction
392{
394 FTransaction* Parent{nullptr};
395 bool bHasStarted{false};
396 bool bHasCommitted{false};
397 bool bHasAborted{false};
399
400#if VERSE_TRANSACTION_HAS_AUTORTFM_ID
401 AutoRTFM::TransactionID AutoRTFMTransactionID{0};
402#endif
403
404 // Note: We can Abort before we Start because of how leniency works. For example, we can't
405 // Start the transaction until the effect token is concrete, but the effect token may become
406 // concrete after failure occurs.
408 {
409 AutoRTFM::UnreachableIfClosed("#jira SOL-8415");
410
412 V_DIE_IF(bHasStarted);
414 bHasStarted = true;
415
416 if (!bHasAborted)
417 {
418 AutoRTFM::ForTheRuntime::StartTransaction();
419 Parent = Context.CurrentTransaction();
420 Context.SetCurrentTransaction(this);
421#if VERSE_TRANSACTION_HAS_AUTORTFM_ID
422 AutoRTFMTransactionID = AutoRTFM::CurrentTransactionID();
423#endif
424 }
425 }
426
427 // We can't call Commit before we Start because we serialize Start then Commit via the effect token.
428 void Commit(FRunningContext Context)
429 {
430 AutoRTFM::UnreachableIfClosed("#jira SOL-8415");
431
432 V_DIE_UNLESS(bHasStarted);
435 bHasCommitted = true;
436#if VERSE_TRANSACTION_HAS_AUTORTFM_ID
437 V_DIE_UNLESS(AutoRTFMTransactionID == AutoRTFM::CurrentTransactionID());
439#endif
440 AutoRTFM::ForTheRuntime::CommitTransaction();
441 if (Parent)
442 {
443 Parent->Log.Append(Context, Log);
444 }
445 Context.SetCurrentTransaction(Parent);
446 }
447
448 // See above comment as to why we might Abort before we start.
449 void Abort(FRunningContext Context)
450 {
451 AutoRTFM::UnreachableIfClosed("#jira SOL-8415");
452
455 bHasAborted = true;
456 if (bHasStarted)
457 {
458 V_DIE_UNLESS(Context.CurrentTransaction() == this);
459
461 {
462#if VERSE_TRANSACTION_HAS_AUTORTFM_ID
463 V_DIE_UNLESS(AutoRTFMTransactionID == AutoRTFM::CurrentTransactionID());
465#endif
466 // Rollback the transaction.
467 AutoRTFM::ForTheRuntime::RollbackTransaction();
468 // Clear the transaction status so the parent transaction(s)
469 // can continue to behave normally.
470 AutoRTFM::ForTheRuntime::ClearTransactionStatus();
472 }
473
474 Log.Backtrack(Context);
475 Context.SetCurrentTransaction(Parent);
476 }
477 else
478 {
480 }
481 }
482
483 template <typename T>
484 void LogBeforeWrite(FAllocationContext Context, TWriteBarrier<T>& Slot)
485 {
486 Log.Add(Context, Slot);
487 }
488};
489
490} // namespace Verse
491#endif // WITH_VERSE_VM
#define FORCENOINLINE
Definition AndroidPlatform.h:142
#define checkSlow(expr)
Definition AssertionMacros.h:332
FPlatformTypes::uint64 uint64
A 64-bit unsigned integer.
Definition Platform.h:1117
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
return true
Definition ExternalRpcRegistry.cpp:601
UE_FORCEINLINE_HINT uint32 GetPtr() const
Definition LockFreeList.h:15
@ Num
Definition MetalRHIPrivate.h:234
ToType BitCast(const FromType &From)
Definition TypeCompatibleBytes.h:167
UE_INTRINSIC_CAST UE_REWRITE constexpr std::remove_reference_t< T > && MoveTemp(T &&Obj) noexcept
Definition UnrealTemplate.h:520
#define V_FORCEINLINE
Definition VVMVerse.h:30
uint32_t uint32
Definition binka_ue_file_header.h:6
Mode
Definition AnimNode_TransitionPoseEvaluator.h:28
FORCEINLINE T * Get(const FObjectPtr &ObjectPtr)
Definition ObjectPtr.h:426
@ Start
Definition GeoEnum.h:100
Definition Archive.h:36
@ false
Definition radaudio_common.h:23
U16 Index
Definition radfft.cpp:71