// Copyright 2022 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/compiler/turboshaft/snapshot-table.h" #include "src/base/optional.h" #include "src/base/vector.h" #include "test/unittests/test-utils.h" namespace v8::internal::compiler::turboshaft { class SnapshotTableTest : public TestWithPlatform {}; TEST_F(SnapshotTableTest, BasicTest) { AccountingAllocator allocator; Zone zone(&allocator, ZONE_NAME); using Key = SnapshotTable::Key; using Snapshot = SnapshotTable::Snapshot; SnapshotTable table(&zone); Key k1 = table.NewKey(1); Key k2 = table.NewKey(2); Key k3 = table.NewKey(3); Key k4 = table.NewKey(4); table.StartNewSnapshot(); EXPECT_EQ(table.Get(k1), 1); EXPECT_EQ(table.Get(k2), 2); EXPECT_EQ(table.Get(k3), 3); EXPECT_EQ(table.Get(k4), 4); table.Set(k1, 10); table.Set(k2, 20); table.Set(k4, 4); EXPECT_EQ(table.Get(k1), 10); EXPECT_EQ(table.Get(k2), 20); EXPECT_EQ(table.Get(k3), 3); EXPECT_EQ(table.Get(k4), 4); Snapshot s1 = table.Seal(); table.StartNewSnapshot(); EXPECT_EQ(table.Get(k1), 1); EXPECT_EQ(table.Get(k2), 2); EXPECT_EQ(table.Get(k3), 3); EXPECT_EQ(table.Get(k4), 4); table.Set(k1, 11); table.Set(k3, 33); EXPECT_EQ(table.Get(k1), 11); EXPECT_EQ(table.Get(k2), 2); EXPECT_EQ(table.Get(k3), 33); EXPECT_EQ(table.Get(k4), 4); Snapshot s2 = table.Seal(); table.StartNewSnapshot(s2); // Assignments of the same value are ignored. EXPECT_EQ(table.Get(k1), 11); table.Set(k1, 11); // Sealing an empty snapshot does not produce a new snapshot. EXPECT_EQ(table.Seal(), s2); table.StartNewSnapshot({s1, s2}, [&](Key key, base::Vector values) { if (key == k1) { EXPECT_EQ(values[0], 10); EXPECT_EQ(values[1], 11); } else if (key == k2) { EXPECT_EQ(values[0], 20); EXPECT_EQ(values[1], 2); } else if (key == k3) { EXPECT_EQ(values[0], 3); EXPECT_EQ(values[1], 33); } else { EXPECT_TRUE(false); } return values[0] + values[1]; }); EXPECT_EQ(table.Get(k1), 21); EXPECT_EQ(table.Get(k2), 22); EXPECT_EQ(table.Get(k3), 36); EXPECT_EQ(table.Get(k4), 4); table.Set(k1, 40); EXPECT_EQ(table.Get(k1), 40); EXPECT_EQ(table.Get(k2), 22); EXPECT_EQ(table.Get(k3), 36); EXPECT_EQ(table.Get(k4), 4); EXPECT_EQ(table.GetPredecessorValue(k1, 0), 10); EXPECT_EQ(table.GetPredecessorValue(k1, 1), 11); EXPECT_EQ(table.GetPredecessorValue(k2, 0), 20); EXPECT_EQ(table.GetPredecessorValue(k2, 1), 2); EXPECT_EQ(table.GetPredecessorValue(k3, 0), 3); EXPECT_EQ(table.GetPredecessorValue(k3, 1), 33); table.Seal(); table.StartNewSnapshot({s1, s2}); EXPECT_EQ(table.Get(k1), 1); EXPECT_EQ(table.Get(k2), 2); EXPECT_EQ(table.Get(k3), 3); EXPECT_EQ(table.Get(k4), 4); table.Seal(); table.StartNewSnapshot(s2); EXPECT_EQ(table.Get(k1), 11); EXPECT_EQ(table.Get(k2), 2); EXPECT_EQ(table.Get(k3), 33); EXPECT_EQ(table.Get(k4), 4); table.Set(k3, 30); EXPECT_EQ(table.Get(k3), 30); Snapshot s4 = table.Seal(); table.StartNewSnapshot({s4, s2}, [&](Key key, base::Vector values) { if (key == k3) { EXPECT_EQ(values[0], 30); EXPECT_EQ(values[1], 33); } else { EXPECT_TRUE(false); } return values[0] + values[1]; }); EXPECT_EQ(table.Get(k1), 11); EXPECT_EQ(table.Get(k2), 2); EXPECT_EQ(table.Get(k3), 63); EXPECT_EQ(table.Get(k4), 4); EXPECT_EQ(table.GetPredecessorValue(k3, 0), 30); EXPECT_EQ(table.GetPredecessorValue(k3, 1), 33); table.Seal(); table.StartNewSnapshot(s2); table.Set(k1, 5); // Creating a new key while the SnapshotTable is already in use. This is the // same as creating the key at the beginning. Key k5 = table.NewKey(-1); EXPECT_EQ(table.Get(k5), -1); table.Set(k5, 42); EXPECT_EQ(table.Get(k5), 42); EXPECT_EQ(table.Get(k1), 5); Snapshot s6 = table.Seal(); // We're merging {s6} and {s1}, to make sure that {s1}'s behavior is correct // with regard to {k5}, which wasn't created yet when {s1} was sealed. table.StartNewSnapshot({s6, s1}, [&](Key key, base::Vector values) { if (key == k1) { EXPECT_EQ(values[1], 10); EXPECT_EQ(values[0], 5); } else if (key == k2) { EXPECT_EQ(values[1], 20); EXPECT_EQ(values[0], 2); } else if (key == k3) { EXPECT_EQ(values[1], 3); EXPECT_EQ(values[0], 33); } else if (key == k5) { EXPECT_EQ(values[0], 42); EXPECT_EQ(values[1], -1); return 127; } else { EXPECT_TRUE(false); } return values[0] + values[1]; }); EXPECT_EQ(table.Get(k1), 15); EXPECT_EQ(table.Get(k2), 22); EXPECT_EQ(table.Get(k3), 36); EXPECT_EQ(table.Get(k4), 4); EXPECT_EQ(table.Get(k5), 127); EXPECT_EQ(table.GetPredecessorValue(k1, 0), 5); EXPECT_EQ(table.GetPredecessorValue(k1, 1), 10); EXPECT_EQ(table.GetPredecessorValue(k2, 0), 2); EXPECT_EQ(table.GetPredecessorValue(k2, 1), 20); EXPECT_EQ(table.GetPredecessorValue(k3, 0), 33); EXPECT_EQ(table.GetPredecessorValue(k3, 1), 3); EXPECT_EQ(table.GetPredecessorValue(k5, 0), 42); EXPECT_EQ(table.GetPredecessorValue(k5, 1), -1); // We're not setting anything else, but the merges should produce entries in // the log. Snapshot s7 = table.Seal(); table.StartNewSnapshot(s7); // We're checking that {s7} did indeed capture the merge entries, despite // that we didn't do any explicit Set. EXPECT_EQ(table.Get(k1), 15); EXPECT_EQ(table.Get(k2), 22); EXPECT_EQ(table.Get(k3), 36); EXPECT_EQ(table.Get(k4), 4); EXPECT_EQ(table.Get(k5), 127); table.Seal(); } TEST_F(SnapshotTableTest, KeyData) { AccountingAllocator allocator; Zone zone(&allocator, ZONE_NAME); struct Data { int x; }; using STable = SnapshotTable; using Key = STable::Key; STable table(&zone); Key k1 = table.NewKey(Data{5}, 1); EXPECT_EQ(k1.data().x, 5); } TEST_F(SnapshotTableTest, ChangeCallback) { AccountingAllocator allocator; Zone zone(&allocator, ZONE_NAME); SnapshotTable table(&zone); using Key = decltype(table)::Key; using Snapshot = decltype(table)::Snapshot; Key k1 = table.NewKey(1); table.StartNewSnapshot(); table.Set(k1, 5); Snapshot s1 = table.Seal(); int invoked = 0; table.StartNewSnapshot({}, [&](Key key, int old_value, int new_value) { invoked++; EXPECT_EQ(key, k1); EXPECT_EQ(old_value, 5); EXPECT_EQ(new_value, 1); }); EXPECT_EQ(invoked, 1); table.Set(k1, 7); Snapshot s2 = table.Seal(); invoked = 0; table.StartNewSnapshot( {s1, s2}, [&](Key key, base::Vector values) { EXPECT_EQ(key, k1); EXPECT_EQ(values[0], 5); EXPECT_EQ(values[1], 7); return 10; }, [&](Key key, int old_value, int new_value) { // We are invoked twice because the table is rolled back first and then // merged. But the only important invariant we should rely on is that // the updates collectively transform the table into the new state. switch (invoked++) { case 0: EXPECT_EQ(key, k1); EXPECT_EQ(old_value, 7); EXPECT_EQ(new_value, 1); break; case 1: EXPECT_EQ(key, k1); EXPECT_EQ(old_value, 1); EXPECT_EQ(new_value, 10); break; default: UNREACHABLE(); } }); EXPECT_EQ(invoked, 2); EXPECT_EQ(table.Get(k1), 10); } TEST_F(SnapshotTableTest, ChangeTrackingSnapshotTable) { AccountingAllocator allocator; Zone zone(&allocator, ZONE_NAME); struct KeyData { int id; }; struct Table : ChangeTrackingSnapshotTable { using ChangeTrackingSnapshotTable::ChangeTrackingSnapshotTable; std::set active_keys; void OnNewKey(Key key, bool value) { if (value) { active_keys.insert(key.data().id); } } void OnValueChange(Key key, bool old_value, bool new_value) { if (old_value && !new_value) { active_keys.erase(key.data().id); } else if (!old_value && new_value) { active_keys.insert(key.data().id); } } } table(&zone); using Key = Table::Key; using Snapshot = Table::Snapshot; Key k1 = table.NewKey(KeyData{5}, true); Key k2 = table.NewKey(KeyData{7}, false); table.StartNewSnapshot(); EXPECT_EQ(table.active_keys, std::set({5})); table.Set(k2, true); EXPECT_EQ(table.active_keys, std::set({5, 7})); Snapshot s1 = table.Seal(); table.StartNewSnapshot(); EXPECT_EQ(table.active_keys, std::set({5})); table.Set(k1, false); EXPECT_EQ(table.active_keys, std::set({})); table.Set(k2, true); EXPECT_EQ(table.active_keys, std::set({7})); Snapshot s2 = table.Seal(); table.StartNewSnapshot({s1, s2}, [&](Key key, base::Vector values) { EXPECT_EQ(values.size(), 2u); return values[0] ^ values[1]; }); EXPECT_EQ(table.active_keys, std::set({5})); table.Seal(); table.StartNewSnapshot({s1, s2}, [&](Key key, base::Vector values) { EXPECT_EQ(values.size(), 2u); return values[0] || values[1]; }); EXPECT_EQ(table.active_keys, std::set({5, 7})); table.Seal(); } } // namespace v8::internal::compiler::turboshaft