mirror of
https://github.com/nodejs/node.git
synced 2025-05-15 14:41:29 +00:00

PR-URL: https://github.com/nodejs/node/pull/49639 Reviewed-By: Jiawen Geng <technicalcute@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
329 lines
10 KiB
C++
329 lines
10 KiB
C++
// 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<int>::Key;
|
|
using Snapshot = SnapshotTable<int>::Snapshot;
|
|
|
|
SnapshotTable<int> 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<const int> 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<const int> 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<const int> 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<int, Data>;
|
|
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<int> 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<const int> 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<Table, bool, KeyData> {
|
|
using ChangeTrackingSnapshotTable::ChangeTrackingSnapshotTable;
|
|
std::set<int> 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<int>({5}));
|
|
table.Set(k2, true);
|
|
EXPECT_EQ(table.active_keys, std::set<int>({5, 7}));
|
|
Snapshot s1 = table.Seal();
|
|
|
|
table.StartNewSnapshot();
|
|
EXPECT_EQ(table.active_keys, std::set<int>({5}));
|
|
table.Set(k1, false);
|
|
EXPECT_EQ(table.active_keys, std::set<int>({}));
|
|
table.Set(k2, true);
|
|
EXPECT_EQ(table.active_keys, std::set<int>({7}));
|
|
Snapshot s2 = table.Seal();
|
|
|
|
table.StartNewSnapshot({s1, s2},
|
|
[&](Key key, base::Vector<const bool> values) {
|
|
EXPECT_EQ(values.size(), 2u);
|
|
return values[0] ^ values[1];
|
|
});
|
|
EXPECT_EQ(table.active_keys, std::set<int>({5}));
|
|
table.Seal();
|
|
|
|
table.StartNewSnapshot({s1, s2},
|
|
[&](Key key, base::Vector<const bool> values) {
|
|
EXPECT_EQ(values.size(), 2u);
|
|
return values[0] || values[1];
|
|
});
|
|
EXPECT_EQ(table.active_keys, std::set<int>({5, 7}));
|
|
table.Seal();
|
|
}
|
|
|
|
} // namespace v8::internal::compiler::turboshaft
|