blob: 5a9e00d9743e47b62fe1b0e6340c84fabb4e54df [file] [log] [blame]
// Copyright 2021 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/codegen/code-stub-assembler.h"
#include "src/codegen/cpu-features.h"
#include "src/objects/objects-inl.h"
#include "src/objects/swiss-name-dictionary-inl.h"
#include "test/cctest/compiler/code-assembler-tester.h"
#include "test/cctest/compiler/function-tester.h"
#include "test/cctest/test-swiss-name-dictionary-infra.h"
#include "test/cctest/test-swiss-name-dictionary-shared-tests.h"
namespace v8 {
namespace internal {
namespace test_swiss_hash_table {
// The non-SIMD SwissNameDictionary implementation requires 64 bit integer
// operations, which CSA/Torque don't offer on 32 bit platforms. Therefore, we
// cannot run the CSA version of the tests on 32 bit platforms. The only
// exception is IA32, where we can use SSE and don't need 64 bit integers.
// TODO(v8:11330) The Torque SIMD implementation is not specific to SSE (like
// the C++ one), but works on other platforms. It should be possible to create a
// workaround where on 32 bit, non-IA32 platforms we use the "portable", non-SSE
// implementation on the C++ side (which uses a group size of 8) and create a
// special version of the SIMD Torque implementation that works for group size 8
// instead of 16.
#if V8_TARGET_ARCH_64_BIT || V8_TARGET_ARCH_IA32
// Executes tests by executing CSA/Torque versions of dictionary operations.
// See RuntimeTestRunner for description of public functions.
class CSATestRunner {
public:
CSATestRunner(Isolate* isolate, int initial_capacity, KeyCache& keys);
// TODO(v8:11330): Remove once CSA implementation has a fallback for
// non-SSSE3/AVX configurations.
static bool IsEnabled() {
#if V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_IA32
CpuFeatures::SupportedFeatures();
return CpuFeatures::IsSupported(CpuFeature::AVX) ||
CpuFeatures::IsSupported(CpuFeature::SSSE3);
#else
// Other 64-bit architectures always support the required operations.
return true;
#endif
}
void Add(Handle<Name> key, Handle<Object> value, PropertyDetails details);
InternalIndex FindEntry(Handle<Name> key);
void Put(InternalIndex entry, Handle<Object> new_value,
PropertyDetails new_details);
void Delete(InternalIndex entry);
void RehashInplace();
void Shrink();
Handle<FixedArray> GetData(InternalIndex entry);
void CheckCounts(base::Optional<int> capacity, base::Optional<int> elements,
base::Optional<int> deleted);
void CheckEnumerationOrder(const std::vector<std::string>& expected_keys);
void CheckCopy();
void VerifyHeap();
void PrintTable();
Handle<SwissNameDictionary> table;
private:
using Label = compiler::CodeAssemblerLabel;
template <class T>
using TVariable = compiler::TypedCodeAssemblerVariable<T>;
void CheckAgainstReference();
void Allocate(Handle<Smi> capacity);
Isolate* isolate_;
// Used to mirror all operations using C++ versions of all operations,
// yielding a reference to compare against.
Handle<SwissNameDictionary> reference_;
// CSA functions execute the corresponding dictionary operation.
compiler::FunctionTester find_entry_ft_;
compiler::FunctionTester get_data_ft_;
compiler::FunctionTester put_ft_;
compiler::FunctionTester delete_ft_;
compiler::FunctionTester add_ft_;
compiler::FunctionTester allocate_ft_;
compiler::FunctionTester get_counts_ft_;
compiler::FunctionTester copy_ft_;
// Used to create the FunctionTesters above.
static Handle<Code> create_get_data(Isolate* isolate);
static Handle<Code> create_find_entry(Isolate* isolate);
static Handle<Code> create_put(Isolate* isolate);
static Handle<Code> create_delete(Isolate* isolate);
static Handle<Code> create_add(Isolate* isolate);
static Handle<Code> create_allocate(Isolate* isolate);
static Handle<Code> create_get_counts(Isolate* isolate);
static Handle<Code> create_copy(Isolate* isolate);
// Number of parameters of each of the tester functions above.
static constexpr int kFindEntryParams = 2; // (table, key)
static constexpr int kGetDataParams = 2; // (table, entry)
static constexpr int kPutParams = 4; // (table, entry, value, details)
static constexpr int kDeleteParams = 2; // (table, entry)
static constexpr int kAddParams = 4; // (table, key, value, details)
static constexpr int kAllocateParams = 1; // (capacity)
static constexpr int kGetCountsParams = 1; // (table)
static constexpr int kCopyParams = 1; // (table)
};
CSATestRunner::CSATestRunner(Isolate* isolate, int initial_capacity,
KeyCache& keys)
: isolate_{isolate},
reference_{isolate_->factory()->NewSwissNameDictionaryWithCapacity(
initial_capacity, AllocationType::kYoung)},
find_entry_ft_(create_find_entry(isolate), kFindEntryParams),
get_data_ft_(create_get_data(isolate), kGetDataParams),
put_ft_{create_put(isolate), kPutParams},
delete_ft_{create_delete(isolate), kDeleteParams},
add_ft_{create_add(isolate), kAddParams},
allocate_ft_{create_allocate(isolate), kAllocateParams},
get_counts_ft_{create_get_counts(isolate), kGetCountsParams},
copy_ft_{create_copy(isolate), kCopyParams} {
Allocate(handle(Smi::FromInt(initial_capacity), isolate));
}
void CSATestRunner::Add(Handle<Name> key, Handle<Object> value,
PropertyDetails details) {
ReadOnlyRoots roots(isolate_);
reference_ =
SwissNameDictionary::Add(isolate_, reference_, key, value, details);
Handle<Smi> details_smi = handle(details.AsSmi(), isolate_);
Handle<Oddball> success =
add_ft_.CallChecked<Oddball>(table, key, value, details_smi);
if (*success == roots.false_value()) {
// |add_ft_| does not resize and indicates the need to do so by returning
// false.
int capacity = table->Capacity();
int used_capacity = table->UsedCapacity();
CHECK_GT(used_capacity + 1,
SwissNameDictionary::MaxUsableCapacity(capacity));
table = SwissNameDictionary::Add(isolate_, table, key, value, details);
}
CheckAgainstReference();
}
void CSATestRunner::Allocate(Handle<Smi> capacity) {
// We must handle |capacity| == 0 specially, because
// AllocateSwissNameDictionary (just like AllocateNameDictionary) always
// returns a non-zero sized table.
if (capacity->value() == 0) {
table = ReadOnlyRoots(isolate_).empty_swiss_property_dictionary_handle();
} else {
table = allocate_ft_.CallChecked<SwissNameDictionary>(capacity);
}
CheckAgainstReference();
}
InternalIndex CSATestRunner::FindEntry(Handle<Name> key) {
Handle<Smi> index = find_entry_ft_.CallChecked<Smi>(table, key);
if (index->value() == SwissNameDictionary::kNotFoundSentinel) {
return InternalIndex::NotFound();
} else {
return InternalIndex(index->value());
}
}
Handle<FixedArray> CSATestRunner::GetData(InternalIndex entry) {
DCHECK(entry.is_found());
return get_data_ft_.CallChecked<FixedArray>(
table, handle(Smi::FromInt(entry.as_int()), isolate_));
}
void CSATestRunner::CheckCounts(base::Optional<int> capacity,
base::Optional<int> elements,
base::Optional<int> deleted) {
Handle<FixedArray> counts = get_counts_ft_.CallChecked<FixedArray>(table);
if (capacity.has_value()) {
CHECK_EQ(Smi::FromInt(capacity.value()), counts->get(0));
}
if (elements.has_value()) {
CHECK_EQ(Smi::FromInt(elements.value()), counts->get(1));
}
if (deleted.has_value()) {
CHECK_EQ(Smi::FromInt(deleted.value()), counts->get(2));
}
CheckAgainstReference();
}
void CSATestRunner::CheckEnumerationOrder(
const std::vector<std::string>& expected_keys) {
// Not implemented in CSA. Making this a no-op (rather than forbidding
// executing CSA tests with this operation) because CheckEnumerationOrder is
// also used by some tests whose main goal is not to test the enumeration
// order.
}
void CSATestRunner::Put(InternalIndex entry, Handle<Object> new_value,
PropertyDetails new_details) {
DCHECK(entry.is_found());
reference_->ValueAtPut(entry, *new_value);
reference_->DetailsAtPut(entry, new_details);
Handle<Smi> entry_smi = handle(Smi::FromInt(entry.as_int()), isolate_);
Handle<Smi> details_smi = handle(new_details.AsSmi(), isolate_);
put_ft_.Call(table, entry_smi, new_value, details_smi);
CheckAgainstReference();
}
void CSATestRunner::Delete(InternalIndex entry) {
DCHECK(entry.is_found());
reference_ = SwissNameDictionary::DeleteEntry(isolate_, reference_, entry);
Handle<Smi> entry_smi = handle(Smi::FromInt(entry.as_int()), isolate_);
table = delete_ft_.CallChecked<SwissNameDictionary>(table, entry_smi);
CheckAgainstReference();
}
void CSATestRunner::RehashInplace() {
// There's no CSA version of this. Use IsRuntimeTest to ensure that we only
// run a test using this if it's a runtime test.
UNREACHABLE();
}
void CSATestRunner::Shrink() {
// There's no CSA version of this. Use IsRuntimeTest to ensure that we only
// run a test using this if it's a runtime test.
UNREACHABLE();
}
void CSATestRunner::CheckCopy() {
Handle<SwissNameDictionary> copy =
copy_ft_.CallChecked<SwissNameDictionary>(table);
CHECK(table->EqualsForTesting(*copy));
}
void CSATestRunner::VerifyHeap() {
#if VERIFY_HEAP
table->SwissNameDictionaryVerify(isolate_, true);
#endif
}
void CSATestRunner::PrintTable() {
#ifdef OBJECT_PRINT
table->SwissNameDictionaryPrint(std::cout);
#endif
}
Handle<Code> CSATestRunner::create_find_entry(Isolate* isolate) {
// TODO(v8:11330): Remove once CSA implementation has a fallback for
// non-SSSE3/AVX configurations.
if (!IsEnabled()) {
return FromCodeT(isolate->builtins()->code_handle(Builtin::kIllegal),
isolate);
}
static_assert(kFindEntryParams == 2); // (table, key)
compiler::CodeAssemblerTester asm_tester(isolate, kFindEntryParams + 1);
CodeStubAssembler m(asm_tester.state());
{
TNode<SwissNameDictionary> table = m.Parameter<SwissNameDictionary>(1);
TNode<Name> key = m.Parameter<Name>(2);
Label done(&m);
TVariable<IntPtrT> entry_var(
m.IntPtrConstant(SwissNameDictionary::kNotFoundSentinel), &m);
// |entry_var| defaults to |kNotFoundSentinel| meaning that one label
// suffices.
m.SwissNameDictionaryFindEntry(table, key, &done, &entry_var, &done);
m.Bind(&done);
m.Return(m.SmiFromIntPtr(entry_var.value()));
}
return asm_tester.GenerateCodeCloseAndEscape();
}
Handle<Code> CSATestRunner::create_get_data(Isolate* isolate) {
static_assert(kGetDataParams == 2); // (table, entry)
compiler::CodeAssemblerTester asm_tester(isolate, kGetDataParams + 1);
CodeStubAssembler m(asm_tester.state());
{
TNode<SwissNameDictionary> table = m.Parameter<SwissNameDictionary>(1);
TNode<IntPtrT> entry = m.SmiToIntPtr(m.Parameter<Smi>(2));
TNode<FixedArray> data = m.AllocateZeroedFixedArray(m.IntPtrConstant(3));
TNode<Object> key = m.LoadSwissNameDictionaryKey(table, entry);
TNode<Object> value = m.LoadValueByKeyIndex(table, entry);
TNode<Smi> details = m.SmiFromUint32(m.LoadDetailsByKeyIndex(table, entry));
m.StoreFixedArrayElement(data, 0, key);
m.StoreFixedArrayElement(data, 1, value);
m.StoreFixedArrayElement(data, 2, details);
m.Return(data);
}
return asm_tester.GenerateCodeCloseAndEscape();
}
Handle<Code> CSATestRunner::create_put(Isolate* isolate) {
static_assert(kPutParams == 4); // (table, entry, value, details)
compiler::CodeAssemblerTester asm_tester(isolate, kPutParams + 1);
CodeStubAssembler m(asm_tester.state());
{
TNode<SwissNameDictionary> table = m.Parameter<SwissNameDictionary>(1);
TNode<Smi> entry = m.Parameter<Smi>(2);
TNode<Object> value = m.Parameter<Object>(3);
TNode<Smi> details = m.Parameter<Smi>(4);
TNode<IntPtrT> entry_intptr = m.SmiToIntPtr(entry);
m.StoreValueByKeyIndex(table, entry_intptr, value,
WriteBarrierMode::UPDATE_WRITE_BARRIER);
m.StoreDetailsByKeyIndex(table, entry_intptr, details);
m.Return(m.UndefinedConstant());
}
return asm_tester.GenerateCodeCloseAndEscape();
}
Handle<Code> CSATestRunner::create_delete(Isolate* isolate) {
// TODO(v8:11330): Remove once CSA implementation has a fallback for
// non-SSSE3/AVX configurations.
if (!IsEnabled()) {
return FromCodeT(isolate->builtins()->code_handle(Builtin::kIllegal),
isolate);
}
static_assert(kDeleteParams == 2); // (table, entry)
compiler::CodeAssemblerTester asm_tester(isolate, kDeleteParams + 1);
CodeStubAssembler m(asm_tester.state());
{
TNode<SwissNameDictionary> table = m.Parameter<SwissNameDictionary>(1);
TNode<IntPtrT> entry = m.SmiToIntPtr(m.Parameter<Smi>(2));
TVariable<SwissNameDictionary> shrunk_table_var(table, &m);
Label done(&m);
m.SwissNameDictionaryDelete(table, entry, &done, &shrunk_table_var);
m.Goto(&done);
m.Bind(&done);
m.Return(shrunk_table_var.value());
}
return asm_tester.GenerateCodeCloseAndEscape();
}
Handle<Code> CSATestRunner::create_add(Isolate* isolate) {
// TODO(v8:11330): Remove once CSA implementation has a fallback for
// non-SSSE3/AVX configurations.
if (!IsEnabled()) {
return FromCodeT(isolate->builtins()->code_handle(Builtin::kIllegal),
isolate);
}
static_assert(kAddParams == 4); // (table, key, value, details)
compiler::CodeAssemblerTester asm_tester(isolate, kAddParams + 1);
CodeStubAssembler m(asm_tester.state());
{
TNode<SwissNameDictionary> table = m.Parameter<SwissNameDictionary>(1);
TNode<Name> key = m.Parameter<Name>(2);
TNode<Object> value = m.Parameter<Object>(3);
TNode<Smi> details = m.Parameter<Smi>(4);
Label needs_resize(&m);
TNode<Int32T> d32 = m.SmiToInt32(details);
TNode<Uint8T> d = m.UncheckedCast<Uint8T>(d32);
m.SwissNameDictionaryAdd(table, key, value, d, &needs_resize);
m.Return(m.TrueConstant());
m.Bind(&needs_resize);
m.Return(m.FalseConstant());
}
return asm_tester.GenerateCodeCloseAndEscape();
}
Handle<Code> CSATestRunner::create_allocate(Isolate* isolate) {
static_assert(kAllocateParams == 1); // (capacity)
compiler::CodeAssemblerTester asm_tester(isolate, kAllocateParams + 1);
CodeStubAssembler m(asm_tester.state());
{
TNode<IntPtrT> capacity = m.SmiToIntPtr(m.Parameter<Smi>(1));
TNode<SwissNameDictionary> table =
m.AllocateSwissNameDictionaryWithCapacity(capacity);
m.Return(table);
}
return asm_tester.GenerateCodeCloseAndEscape();
}
Handle<Code> CSATestRunner::create_get_counts(Isolate* isolate) {
static_assert(kGetCountsParams == 1); // (table)
compiler::CodeAssemblerTester asm_tester(isolate, kGetCountsParams + 1);
CodeStubAssembler m(asm_tester.state());
{
TNode<SwissNameDictionary> table = m.Parameter<SwissNameDictionary>(1);
TNode<IntPtrT> capacity =
m.ChangeInt32ToIntPtr(m.LoadSwissNameDictionaryCapacity(table));
TNode<IntPtrT> elements =
m.LoadSwissNameDictionaryNumberOfElements(table, capacity);
TNode<IntPtrT> deleted =
m.LoadSwissNameDictionaryNumberOfDeletedElements(table, capacity);
TNode<FixedArray> results = m.AllocateZeroedFixedArray(m.IntPtrConstant(3));
auto check_and_add = [&](TNode<IntPtrT> value, int array_index) {
CSA_DCHECK(&m, m.UintPtrGreaterThanOrEqual(value, m.IntPtrConstant(0)));
CSA_DCHECK(&m, m.UintPtrLessThanOrEqual(
value, m.IntPtrConstant(Smi::kMaxValue)));
TNode<Smi> smi = m.SmiFromIntPtr(value);
m.StoreFixedArrayElement(results, array_index, smi);
};
check_and_add(capacity, 0);
check_and_add(elements, 1);
check_and_add(deleted, 2);
m.Return(results);
}
return asm_tester.GenerateCodeCloseAndEscape();
}
Handle<Code> CSATestRunner::create_copy(Isolate* isolate) {
static_assert(kCopyParams == 1); // (table)
compiler::CodeAssemblerTester asm_tester(isolate, kCopyParams + 1);
CodeStubAssembler m(asm_tester.state());
{
TNode<SwissNameDictionary> table = m.Parameter<SwissNameDictionary>(1);
m.Return(m.CopySwissNameDictionary(table));
}
return asm_tester.GenerateCodeCloseAndEscape();
}
void CSATestRunner::CheckAgainstReference() {
CHECK(table->EqualsForTesting(*reference_));
}
// Executes the tests defined in test-swiss-name-dictionary-shared-tests.h as if
// they were defined in this file, using the CSATestRunner. See comments in
// test-swiss-name-dictionary-shared-tests.h and in
// swiss-name-dictionary-infra.h for details.
const char kCSATestFileName[] = __FILE__;
SharedSwissTableTests<CSATestRunner, kCSATestFileName> execute_shared_tests_csa;
#endif
} // namespace test_swiss_hash_table
} // namespace internal
} // namespace v8