v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
snapshot-table.h
Go to the documentation of this file.
1// Copyright 2022 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifndef V8_COMPILER_TURBOSHAFT_SNAPSHOT_TABLE_H_
6#define V8_COMPILER_TURBOSHAFT_SNAPSHOT_TABLE_H_
7
8#include <iostream>
9#include <limits>
10
11#include "src/base/iterator.h"
15
16// A `SnapshotTable` stores a mapping from keys to values and creates snapshots,
17// which capture the current state efficiently and allow us to return to a
18// previous snapshot later. It is optimized for the case where we switch between
19// similar snapshots with a closeby common ancestor.
20//
21// Complexity:
22// creating a snapshot linear in the number of `Set` operations between the
23// current state and the common ancestor of all
24// predecessors and the current state, plus the `Set`
25// operations from the common ancestor to all
26// predecessors.
27// Get() O(1)
28// Set() O(1) + operator== for Value
29// Seal() O(1)
30// NewKey() O(1)
31// GetPredecessorValue() O(1)
33
34struct NoKeyData {};
35
37 template <class Key, class Value>
38 void operator()(Key key, const Value& old_value,
39 const Value& new_value) const {}
40};
41
42template <class Value, class KeyData>
43class SnapshotTable;
44
45// Place `KeyData` in a superclass to benefit from empty-base optimization.
46template <class Value, class KeyData>
49 // `merge_offset` is the offset in `merge_values_` where we store the
50 // merged values. It is used during merging (to know what to merge) and when
51 // calling GetPredecessorValue.
53 // Used during merging: the index of the predecessor for which we last
54 // recorded a value. This allows us to only use the last value for a given
55 // predecessor and skip over all earlier ones.
57
58 explicit SnapshotTableEntry(Value value, KeyData data)
59 : KeyData(std::move(data)), value(std::move(value)) {}
60
61 static constexpr uint32_t kNoMergeOffset =
62 std::numeric_limits<uint32_t>::max();
63 static constexpr uint32_t kNoMergedPredecessor =
64 std::numeric_limits<uint32_t>::max();
65};
66
67// A `SnapshotTableKey` identifies an entry in the `SnapshotTable`. For better
68// performance, keys always have identity. The template parameter `KeyData` can
69// be used to embed additional data in the keys. A Key is implemented as a
70// pointer into the table, which also contains the `KeyData`. Therefore, keys
71// have pointer-size and are cheap to copy.
72template <class Value, class KeyData>
74 public:
75 bool operator==(SnapshotTableKey other) const {
76 return entry_ == other.entry_;
77 }
78 const KeyData& data() const { return *entry_; }
79 KeyData& data() { return *entry_; }
80 SnapshotTableKey() : entry_(nullptr) {}
81
82 bool valid() const { return entry_ != nullptr; }
83
84 private:
85 friend class SnapshotTable<Value, KeyData>;
89};
90
91template <class Value, class KeyData = NoKeyData>
93 private:
94 struct LogEntry;
95 struct SnapshotData;
96
97 public:
100
101 // A `Snapshot` captures the state of the `SnapshotTable`.
102 // A `Snapshot` is implemented as a pointer to internal data and is therefore
103 // cheap to copy.
104 class MaybeSnapshot;
105 class Snapshot {
106 public:
107 bool operator==(Snapshot other) const { return data_ == other.data_; }
108
109 private:
112
114 explicit Snapshot(SnapshotData& data) : data_(&data) {}
115 explicit Snapshot(SnapshotData* data) : data_(data) {}
116 };
117
119 public:
120 bool has_value() const { return data_ != nullptr; }
121 Snapshot value() const {
122 DCHECK(has_value());
123 return Snapshot{data_};
124 }
125
126 void Set(Snapshot snapshot) { data_ = snapshot.data_; }
127
128 MaybeSnapshot() = default;
129 explicit MaybeSnapshot(Snapshot snapshot) : data_(snapshot.data_) {}
130
131 private:
132 SnapshotData* data_ = nullptr;
133 };
134
135 // A new Snapshot is based on a list of predecessor Snapshots. If no
136 // predecessor is given, the new Snapshot is based on the initial state of the
137 // table. A single predecessor Snapshot resets the table to exactly this
138 // Snapshot. In the case of multiple Snapshots, a merge function is used to
139 // unify values that were set since the last common ancestor snapshot.
140 // The previous Snapshot needs to be closed using Seal() before another one
141 // can be created.
142 // The function `change_callback` is invoked for every atomic update to a
143 // table entry as part of switching to the new snapshot and merging.
144 // Note that the callback might be invoked multiple times for the same key,
145 // because we first roll-back changes to the common ancestor and then apply
146 // the merge function. The second update will have the new value of the first
147 // update as old value. We should not rely on the exact sequence of updates,
148 // only on the fact that the updates collectively transform the table into the
149 // new state. The motivation for this feature are secondary indices that need
150 // to be kept in sync with the main table.
151 template <class ChangeCallback = NoChangeCallback>
153 const ChangeCallback& change_callback = {})
154 requires(std::is_invocable_v<ChangeCallback, Key, Value, Value>)
155 {
157 MoveToNewSnapshot(predecessors, change_callback);
158#ifdef DEBUG
159 snapshot_was_created_with_merge = false;
160#endif
161 }
162 template <class ChangeCallback = NoChangeCallback>
163 void StartNewSnapshot(std::initializer_list<Snapshot> predecessors = {},
164 const ChangeCallback& change_callback = {})
165 requires(std::is_invocable_v<ChangeCallback, Key, Value, Value>)
166 {
167 StartNewSnapshot(base::VectorOf(predecessors), change_callback);
168 }
169 template <class ChangeCallback = NoChangeCallback>
171 const ChangeCallback& change_callback = {})
172 requires(std::is_invocable_v<ChangeCallback, Key, Value, Value>)
173 {
174 StartNewSnapshot({parent}, change_callback);
175 }
176 template <class MergeFun, class ChangeCallback = NoChangeCallback>
178 const MergeFun& merge_fun,
179 const ChangeCallback& change_callback = {})
180 requires(std::is_invocable_v<MergeFun, Key, base::Vector<const Value>> &&
181 std::is_invocable_v<ChangeCallback, Key, Value, Value>)
182 {
183 StartNewSnapshot(predecessors, change_callback);
184 MergePredecessors(predecessors, merge_fun, change_callback);
185#ifdef DEBUG
186 snapshot_was_created_with_merge = true;
187#endif
188 }
189 template <class MergeFun, class ChangeCallback = NoChangeCallback>
190 void StartNewSnapshot(std::initializer_list<Snapshot> predecessors,
191 const MergeFun& merge_fun,
192 const ChangeCallback& change_callback = {})
193 requires(std::is_invocable_v<MergeFun, Key, base::Vector<const Value>> &&
194 std::is_invocable_v<ChangeCallback, Key, Value, Value>)
195 {
196 StartNewSnapshot(base::VectorOf(predecessors), merge_fun, change_callback);
197 }
198
200 current_snapshot_->Seal(log_.size());
201 // Reseting the entries' `merge_offset` and `last_merged_predecessor`
202 // fields, so that they are cleared for the next Merge.
203 for (TableEntry* entry : merging_entries_) {
204 entry->last_merged_predecessor = kNoMergedPredecessor;
205 entry->merge_offset = kNoMergeOffset;
206 }
207 merge_values_.clear();
208 merging_entries_.clear();
209
210 // Optimization: If nothing changed in the new snapshot, we discard it and
211 // use its parent instead.
215 snapshots_.pop_back();
216 current_snapshot_ = parent;
217 return Snapshot{*parent};
218 }
220 }
221
222 const Value& Get(Key key) const { return key.entry_->value; }
223
224 // Returns the value associated to {key} in its {predecessor_index}th
225 // predecessor (where "predecessor" refers to the predecessors that were
226 // passed to StartNewSnapshot when creating the current snapshot).
227 // This function should only be used if the snapshot was started with a merge
228 // function.
229 // If {key} wasn't merged but was Set in the current snapshot, then
230 // the newly set value will be returned rather than the predecessor value.
231 const Value& GetPredecessorValue(Key key, int predecessor_index) {
233 DCHECK(snapshot_was_created_with_merge);
234 if (key.entry_->merge_offset == kNoMergeOffset) return Get(key);
235 return merge_values_[key.entry_->merge_offset + predecessor_index];
236 }
237
238 // {Set} returns whether the {new_value} is different from the previous value.
239 bool Set(Key key, Value new_value) {
241 if (key.entry_->value == new_value) return false;
242 log_.push_back(LogEntry{*key.entry_, key.entry_->value, new_value});
243 key.entry_->value = new_value;
244 return true;
245 }
246
247 explicit SnapshotTable(Zone* zone) : zone_(zone) {
248 root_snapshot_ = &NewSnapshot(nullptr);
251 }
252
253 // The initial value is independent of the snapshot mechanism. Creating a key
254 // with a certain initial value later has the same effect as creating the key
255 // before all modifications to the table.
256 // Keys have identity, and the data embedded in the key is mutable.
257 Key NewKey(KeyData data, Value initial_value = Value{}) {
258 return Key{table_.emplace_back(
259 TableEntry{std::move(initial_value), std::move(data)})};
260 }
261 Key NewKey(Value initial_value = Value{}) {
262 return NewKey(KeyData{}, initial_value);
263 }
264
265 // Returns true if {current_snapshot_} is sealed.
266 bool IsSealed() { return current_snapshot_->IsSealed(); }
267
268 private:
272 // While logically each snapshot has its own log, we allocate the memory as a
273 // single global log with each snapshot pointing to a section of it to reduce
274 // the number of allocations.
278
279 // The following members are only used temporarily during a merge operation
280 // or when creating a new snapshot.
281 // They are declared here to recycle the memory, avoiding repeated
282 // Zone-allocation.
286
287#ifdef DEBUG
288 bool snapshot_was_created_with_merge = false;
289#endif
290
292 return snapshots_.emplace_back(parent, log_.size());
293 }
294
296 return base::VectorOf(&log_[s->log_begin], s->log_end - s->log_begin);
297 }
298
299 template <class ChangeCallback = NoChangeCallback>
300 void RevertCurrentSnapshot(ChangeCallback& change_callback) {
303 for (const LogEntry& entry : base::Reversed(log_entries)) {
304 DCHECK_EQ(entry.table_entry.value, entry.new_value);
305 DCHECK_NE(entry.new_value, entry.old_value);
306 change_callback(Key{entry.table_entry}, entry.new_value, entry.old_value);
307 entry.table_entry.value = entry.old_value;
308 }
311 }
312
313 template <class ChangeCallback = NoChangeCallback>
314 void ReplaySnapshot(SnapshotData* snapshot, ChangeCallback& change_callback) {
315 DCHECK_EQ(snapshot->parent, current_snapshot_);
316 for (const LogEntry& entry : LogEntries(snapshot)) {
317 DCHECK_EQ(entry.table_entry.value, entry.old_value);
318 DCHECK_NE(entry.new_value, entry.old_value);
319 change_callback(Key{entry.table_entry}, entry.old_value, entry.new_value);
320 entry.table_entry.value = entry.new_value;
321 }
322 current_snapshot_ = snapshot;
323 }
324
325 void RecordMergeValue(TableEntry& entry, const Value& value,
326 uint32_t predecessor_index, uint32_t predecessor_count);
327 template <class ChangeCallback>
329 const ChangeCallback& change_callback);
330 template <class MergeFun, class ChangeCallback>
332 const MergeFun& merge_fun,
333 const ChangeCallback& change_callback);
334
335 static constexpr uint32_t kNoMergeOffset =
336 std::numeric_limits<uint32_t>::max();
337 static constexpr uint32_t kNoMergedPredecessor =
338 std::numeric_limits<uint32_t>::max();
339};
340
341template <class Value, class KeyData>
347
348template <class Value, class KeyData>
351
352 const uint32_t depth = parent ? parent->depth + 1 : 0;
353 size_t log_begin;
354 size_t log_end = kInvalidOffset;
355
356 static constexpr size_t kInvalidOffset = std::numeric_limits<size_t>::max();
357
358 SnapshotData(SnapshotData* parent, size_t log_begin)
359 : parent(parent), log_begin(log_begin) {}
360
362 SnapshotData* self = this;
363 while (other->depth > self->depth) other = other->parent;
364 while (self->depth > other->depth) self = self->parent;
365 while (other != self) {
366 self = self->parent;
367 other = other->parent;
368 }
369 return self;
370 }
371 void Seal(size_t end) {
372 DCHECK_WITH_MSG(!IsSealed(), "A Snapshot can only be sealed once");
373 this->log_end = end;
374 }
375
376 bool IsSealed() const { return log_end != kInvalidOffset; }
377};
378
379template <class Value, class KeyData>
381 TableEntry& entry, const Value& value, uint32_t predecessor_index,
382 uint32_t predecessor_count) {
383 if (predecessor_index == entry.last_merged_predecessor) {
384 DCHECK_NE(entry.merge_offset, kNoMergeOffset);
385 // We already recorded a later value for this predecessor, so we should skip
386 // earlier values.
387 return;
388 }
389 if (entry.merge_offset == kNoMergeOffset) {
390 // Allocate space for the merge values. All the merge values are initialized
391 // to the value from the parent snapshot. This way, we get the right value
392 // for predecessors that did not change the value.
393 DCHECK_EQ(entry.last_merged_predecessor, kNoMergedPredecessor);
394 CHECK_LE(merge_values_.size() + predecessor_count,
395 std::numeric_limits<uint32_t>::max());
396 entry.merge_offset = static_cast<uint32_t>(merge_values_.size());
397 merging_entries_.push_back(&entry);
398 merge_values_.insert(merge_values_.end(), predecessor_count, entry.value);
399 }
400 merge_values_[entry.merge_offset + predecessor_index] = value;
401 entry.last_merged_predecessor = predecessor_index;
402}
403
404// This function prepares the SnapshotTable to start a new snapshot whose
405// predecessors are `predecessors`. To do this, it resets and replay snapshots
406// in between the `current_snapshot_` and the position of the new snapshot. For
407// instance:
408//
409// S0
410// / \
411// S1 S3
412// | \
413// S2 S4
414// / \
415// S5 S6
416// If `predecessors` are S5 and S6, and `current_snapshot_` is S2, we:
417//
418// - First find the common ancestor of S5 and S6 (it's S4). This will be the
419// parent snapshot of the new snapshot.
420// - Find the common ancestor of S4 and the current snapshot S2 (it's S0).
421// - Roll back S2 and S1 to reach S0
422// - Replay S3 and S4 go be in the state of S4 (the common ancestor of
423// `predecessors`).
424// - Start creating a new snapshot with parent S4.
425template <class Value, class KeyData>
426template <class ChangeCallback>
429 base::Vector<const Snapshot> predecessors,
430 const ChangeCallback& change_callback) {
432 current_snapshot_->IsSealed(),
433 "A new Snapshot was opened before the previous Snapshot was sealed");
434
435 SnapshotData* common_ancestor;
436 if (predecessors.empty()) {
437 common_ancestor = root_snapshot_;
438 } else {
439 common_ancestor = predecessors.first().data_;
440 for (Snapshot s : predecessors.SubVectorFrom(1)) {
441 common_ancestor = common_ancestor->CommonAncestor(s.data_);
442 }
443 }
444 SnapshotData* go_back_to = common_ancestor->CommonAncestor(current_snapshot_);
445 while (current_snapshot_ != go_back_to) {
446 RevertCurrentSnapshot(change_callback);
447 }
448 {
449 // Replay to common_ancestor.
450 path_.clear();
451 for (SnapshotData* s = common_ancestor; s != go_back_to; s = s->parent) {
452 path_.push_back(s);
453 }
454 for (SnapshotData* s : base::Reversed(path_)) {
455 ReplaySnapshot(s, change_callback);
456 }
457 }
458
459 DCHECK_EQ(current_snapshot_, common_ancestor);
460 SnapshotData& new_snapshot = NewSnapshot(common_ancestor);
461 current_snapshot_ = &new_snapshot;
462 return new_snapshot;
463}
464
465// Merges all entries modified in `predecessors` since the last common ancestor
466// by adding them to the current snapshot.
467template <class Value, class KeyData>
468template <class MergeFun, class ChangeCallback>
470 base::Vector<const Snapshot> predecessors, const MergeFun& merge_fun,
471 const ChangeCallback& change_callback) {
472 CHECK_LE(predecessors.size(), std::numeric_limits<uint32_t>::max());
473 uint32_t predecessor_count = static_cast<uint32_t>(predecessors.size());
474 if (predecessor_count < 1) return;
475
476 // The merging works by reserving `predecessor_count` many slots in
477 // `merge_values_` for every key that we find while going through the
478 // predecessor logs. There, we place the values of the corresponding
479 // predecessors, so that we can finally call the `merge_fun` by creating a
480 // `base::Vector` pointing to the collected values inside of `merge_values_`.
481 DCHECK(merge_values_.empty());
482 DCHECK(merging_entries_.empty());
483 SnapshotData* common_ancestor = current_snapshot_->parent;
484
485 // Collect all the entries that require merging. For this, we walk the logs of
486 // the predecessors backwards until reaching the common ancestor.
487 for (uint32_t i = 0; i < predecessor_count; ++i) {
488 for (SnapshotData* predecessor = predecessors[i].data_;
489 predecessor != common_ancestor; predecessor = predecessor->parent) {
490 base::Vector<LogEntry> log_entries = LogEntries(predecessor);
491 for (const LogEntry& entry : base::Reversed(log_entries)) {
492 RecordMergeValue(entry.table_entry, entry.new_value, i,
493 predecessor_count);
494 }
495 }
496 }
497 // Actually perform the merging by calling the merge function and modifying
498 // the table.
499 for (TableEntry* entry : merging_entries_) {
500 Key key{*entry};
501 Value value = merge_fun(
502 key, base::VectorOf<const Value>(&merge_values_[entry->merge_offset],
503 predecessor_count));
504 Value old_value = entry->value;
505 if (Set(key, std::move(value))) {
506 change_callback(key, old_value, entry->value);
507 }
508 }
509}
510
511// ChangeTrackingSnapshotTable extends SnapshotTable by automatically invoking
512// OnNewKey and OnValueChange on the subclass whenever the table state changes.
513// This makes it easy to maintain consistent additional tables for faster lookup
514// of the state of the snapshot table, similar to how secondary indices can
515// speed-up lookups in database tables.
516// For example usage, see TEST_F(SnapshotTableTest, ChangeTrackingSnapshotTable)
517// in test/unittests/compiler/turboshaft/snapshot-table-unittest.cc.
518template <class Derived, class Value, class KeyData = NoKeyData>
519class ChangeTrackingSnapshotTable : public SnapshotTable<Value, KeyData> {
520 public:
522 using Super::Super;
523 using typename Super::Key;
524 using typename Super::Snapshot;
525
528 predecessors,
529 [this](Key key, const Value& old_value, const Value& new_value) {
530 static_cast<Derived*>(this)->OnValueChange(key, old_value, new_value);
531 });
532 }
533 void StartNewSnapshot(std::initializer_list<Snapshot> predecessors = {}) {
534 StartNewSnapshot(base::VectorOf(predecessors));
535 }
536 void StartNewSnapshot(Snapshot parent) { StartNewSnapshot({parent}); }
537 template <class MergeFun>
539 const MergeFun& merge_fun)
540 requires(std::is_invocable_v<MergeFun, Key, base::Vector<const Value>>)
541 {
543 predecessors, merge_fun,
544 [this](Key key, const Value& old_value, const Value& new_value) {
545 static_cast<Derived*>(this)->OnValueChange(key, old_value, new_value);
546 });
547 }
548 template <class MergeFun>
549 void StartNewSnapshot(std::initializer_list<Snapshot> predecessors,
550 const MergeFun& merge_fun)
551 requires(std::is_invocable_v<MergeFun, Key, base::Vector<const Value>>)
552 {
553 StartNewSnapshot(base::VectorOf(predecessors), merge_fun);
554 }
555
556 void Set(Key key, Value new_value) {
557 Value old_value = Super::Get(key);
558 if (Super::Set(key, std::move(new_value))) {
559 static_cast<Derived*>(this)->OnValueChange(key, old_value,
560 Super::Get(key));
561 }
562 }
563
564 void SetNoNotify(Key key, Value new_value) {
565 Super::Set(key, std::move(new_value));
566 }
567
568 Key NewKey(KeyData data, Value initial_value = Value{}) {
569 Key key = Super::NewKey(std::move(data), std::move(initial_value));
570 static_cast<Derived*>(this)->OnNewKey(key, Super::Get(key));
571 return key;
572 }
573 Key NewKey(Value initial_value = Value{}) {
574 return NewKey(KeyData{}, initial_value);
575 }
576};
577
578} // namespace v8::internal::compiler::turboshaft
579
580#endif // V8_COMPILER_TURBOSHAFT_SNAPSHOT_TABLE_H_
uint8_t data_[MAX_STACK_LENGTH]
constexpr bool empty() const
Definition vector.h:73
constexpr size_t size() const
Definition vector.h:70
Vector< T > SubVectorFrom(size_t from) const
Definition vector.h:46
void StartNewSnapshot(base::Vector< const Snapshot > predecessors, const MergeFun &merge_fun)
void StartNewSnapshot(std::initializer_list< Snapshot > predecessors, const MergeFun &merge_fun)
void StartNewSnapshot(std::initializer_list< Snapshot > predecessors={})
Key NewKey(KeyData data, Value initial_value=Value{})
void StartNewSnapshot(base::Vector< const Snapshot > predecessors)
bool operator==(SnapshotTableKey other) const
SnapshotTableEntry< Value, KeyData > * entry_
SnapshotTableKey(SnapshotTableEntry< Value, KeyData > &entry)
base::Vector< LogEntry > LogEntries(SnapshotData *s)
void RecordMergeValue(TableEntry &entry, const Value &value, uint32_t predecessor_index, uint32_t predecessor_count)
void StartNewSnapshot(Snapshot parent, const ChangeCallback &change_callback={})
void MergePredecessors(base::Vector< const Snapshot > predecessors, const MergeFun &merge_fun, const ChangeCallback &change_callback)
Key NewKey(KeyData data, Value initial_value=Value{})
void StartNewSnapshot(std::initializer_list< Snapshot > predecessors, const MergeFun &merge_fun, const ChangeCallback &change_callback={})
SnapshotData & NewSnapshot(SnapshotData *parent)
void StartNewSnapshot(base::Vector< const Snapshot > predecessors, const MergeFun &merge_fun, const ChangeCallback &change_callback={})
void RevertCurrentSnapshot(ChangeCallback &change_callback)
void StartNewSnapshot(std::initializer_list< Snapshot > predecessors={}, const ChangeCallback &change_callback={})
void ReplaySnapshot(SnapshotData *snapshot, ChangeCallback &change_callback)
SnapshotTableEntry< Value, KeyData > TableEntry
SnapshotTableKey< Value, KeyData > Key
const Value & GetPredecessorValue(Key key, int predecessor_index)
void StartNewSnapshot(base::Vector< const Snapshot > predecessors, const ChangeCallback &change_callback={})
SnapshotData & MoveToNewSnapshot(base::Vector< const Snapshot > predecessors, const ChangeCallback &change_callback)
int end
STL namespace.
auto Reversed(T &t)
Definition iterator.h:105
constexpr Vector< T > VectorOf(T *start, size_t size)
Definition vector.h:360
return value
Definition map-inl.h:893
#define DCHECK_WITH_MSG(condition, msg)
Definition logging.h:182
#define CHECK_LE(lhs, rhs)
#define DCHECK_NOT_NULL(val)
Definition logging.h:492
#define DCHECK_NE(v1, v2)
Definition logging.h:486
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_EQ(v1, v2)
Definition logging.h:485
void operator()(Key key, const Value &old_value, const Value &new_value) const