Files
sdk/runtime/platform/utils.cc
T
Tess Strickland 4f925105cf [vm] Use a hash map for larger type argument instantiation caches.
Previously, the VM used a linear array to cache previous instantiations
of a type arguments object. Now once the cache hits a certain number
of occupied entries, the VM changes to using a hash-based approach.

The InstantiateTypeArguments stubs have not yet been updated to
traverse the hash-based cache, so once the cache has grown too large,
all attempts at instantiations, even those that are in the cache, go to
the runtime. Thus, until the stubs are updated, this is only an
improvement if the cost of traversing the linear cache dominates the
cost of making a runtime call. Our benchmarks see a ~40% performance
regression for hash-based caches of size 100 but a ~400% performance
improvement for hash-based caches of size 1000. Thus, we currently
split the difference and set the maximum size of linear caches to 500.

TEST=vm/cc/TypeArguments_Cache_ManyInstantiations

Bug: https://github.com/dart-lang/sdk/issues/48344
Change-Id: I7f1376943523bb5bcd8b175cfb1936779ea73d60
Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-dwarf-linux-product-x64-try,vm-kernel-precomp-linux-product-x64-try,vm-kernel-precomp-linux-release-x64-try,vm-kernel-precomp-nnbd-mac-release-arm64-try,vm-kernel-precomp-nnbd-linux-release-simarm_x64-try,vm-kernel-precomp-linux-release-simarm-try,vm-kernel-precomp-nnbd-linux-release-x64-try,vm-kernel-precomp-nnbd-linux-release-simarm64-try,vm-kernel-precomp-nnbd-linux-debug-simriscv64-try,vm-kernel-precomp-tsan-linux-release-x64-try,vm-kernel-tsan-linux-release-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/265325
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
2022-11-28 15:00:20 +00:00

405 lines
13 KiB
C++

// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "platform/utils.h"
#include "platform/allocation.h"
#include "platform/globals.h"
#if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_MACOS) || \
defined(DART_HOST_OS_ANDROID) || defined(DART_HOST_OS_FUCHSIA)
#include <dlfcn.h>
#endif
namespace dart {
int Utils::CountLeadingZeros64(uint64_t x) {
#if defined(ARCH_IS_32_BIT)
const uint32_t x_hi = static_cast<uint32_t>(x >> 32);
if (x_hi != 0) {
return CountLeadingZeros32(x_hi);
}
return 32 + CountLeadingZeros32(static_cast<uint32_t>(x));
#elif defined(DART_HOST_OS_WINDOWS)
unsigned long position; // NOLINT
return (_BitScanReverse64(&position, x) == 0)
? 64
: 63 - static_cast<int>(position);
#else
return x == 0 ? 64 : __builtin_clzll(x);
#endif
}
int Utils::CountLeadingZeros32(uint32_t x) {
#if defined(DART_HOST_OS_WINDOWS)
unsigned long position; // NOLINT
return (_BitScanReverse(&position, x) == 0) ? 32
: 31 - static_cast<int>(position);
#else
return x == 0 ? 32 : __builtin_clz(x);
#endif
}
int Utils::CountTrailingZeros64(uint64_t x) {
#if defined(ARCH_IS_32_BIT)
const uint32_t x_lo = static_cast<uint32_t>(x);
if (x_lo != 0) {
return CountTrailingZeros32(x_lo);
}
return 32 + CountTrailingZeros32(static_cast<uint32_t>(x >> 32));
#elif defined(DART_HOST_OS_WINDOWS)
unsigned long position; // NOLINT
return (_BitScanForward64(&position, x) == 0) ? 64
: static_cast<int>(position);
#else
return x == 0 ? 64 : __builtin_ctzll(x);
#endif
}
int Utils::CountTrailingZeros32(uint32_t x) {
#if defined(DART_HOST_OS_WINDOWS)
unsigned long position; // NOLINT
return (_BitScanForward(&position, x) == 0) ? 32 : static_cast<int>(position);
#else
return x == 0 ? 32 : __builtin_ctz(x);
#endif
}
uint64_t Utils::ReverseBits64(uint64_t x) {
x = ((x >> 32) & 0x00000000ffffffff) | (x << 32);
x = ((x >> 16) & 0x0000ffff0000ffff) | ((x & 0x0000ffff0000ffff) << 16);
x = ((x >> 8) & 0x00ff00ff00ff00ff) | ((x & 0x00ff00ff00ff00ff) << 8);
x = ((x >> 4) & 0x0f0f0f0f0f0f0f0f) | ((x & 0x0f0f0f0f0f0f0f0f) << 4);
x = ((x >> 2) & 0x3333333333333333) | ((x & 0x3333333333333333) << 2);
x = ((x >> 1) & 0x5555555555555555) | ((x & 0x5555555555555555) << 1);
return x;
}
uint32_t Utils::ReverseBits32(uint32_t x) {
x = ((x >> 16) & 0x0000ffff) | ((x & 0x0000ffff) << 16);
x = ((x >> 8) & 0x00ff00ff) | ((x & 0x00ff00ff) << 8);
x = ((x >> 4) & 0x0f0f0f0f) | ((x & 0x0f0f0f0f) << 4);
x = ((x >> 2) & 0x33333333) | ((x & 0x33333333) << 2);
x = ((x >> 1) & 0x55555555) | ((x & 0x55555555) << 1);
return x;
}
// Implementation according to H.S.Warren's "Hacker's Delight"
// (Addison Wesley, 2002) Chapter 10 and T.Grablund, P.L.Montogomery's
// "Division by Invariant Integers Using Multiplication" (PLDI 1994).
void Utils::CalculateMagicAndShiftForDivRem(int64_t divisor,
int64_t* magic,
int64_t* shift) {
ASSERT(divisor <= -2 || divisor >= 2);
/* The magic number M and shift S can be calculated in the following way:
* Let nc be the most positive value of numerator(n) such that nc = kd - 1,
* where divisor(d) >= 2.
* Let nc be the most negative value of numerator(n) such that nc = kd + 1,
* where divisor(d) <= -2.
* Thus nc can be calculated like:
* nc = exp + exp % d - 1, where d >= 2 and exp = 2^63.
* nc = -exp + (exp + 1) % d, where d >= 2 and exp = 2^63.
*
* So the shift p is the smallest p satisfying
* 2^p > nc * (d - 2^p % d), where d >= 2
* 2^p > nc * (d + 2^p % d), where d <= -2.
*
* The magic number M is calculated by
* M = (2^p + d - 2^p % d) / d, where d >= 2
* M = (2^p - d - 2^p % d) / d, where d <= -2.
*/
int64_t p = 63;
const uint64_t exp = 1LL << 63;
// Initialize the computations.
uint64_t abs_d = (divisor >= 0) ? divisor : -static_cast<uint64_t>(divisor);
uint64_t sign_bit = static_cast<uint64_t>(divisor) >> 63;
uint64_t tmp = exp + sign_bit;
uint64_t abs_nc = tmp - 1 - (tmp % abs_d);
uint64_t quotient1 = exp / abs_nc;
uint64_t remainder1 = exp % abs_nc;
uint64_t quotient2 = exp / abs_d;
uint64_t remainder2 = exp % abs_d;
// To avoid handling both positive and negative divisor,
// "Hacker's Delight" introduces a method to handle these
// two cases together to avoid duplication.
uint64_t delta;
do {
p++;
quotient1 = 2 * quotient1;
remainder1 = 2 * remainder1;
if (remainder1 >= abs_nc) {
quotient1++;
remainder1 = remainder1 - abs_nc;
}
quotient2 = 2 * quotient2;
remainder2 = 2 * remainder2;
if (remainder2 >= abs_d) {
quotient2++;
remainder2 = remainder2 - abs_d;
}
delta = abs_d - remainder2;
} while (quotient1 < delta || (quotient1 == delta && remainder1 == 0));
*magic = (divisor > 0) ? (quotient2 + 1) : (-quotient2 - 1);
*shift = p - 64;
}
// This implementation is based on the public domain MurmurHash
// version 2.0. The constants M and R have been determined
// to work well experimentally.
static constexpr uint32_t kStringHashM = 0x5bd1e995;
static constexpr int kStringHashR = 24;
// hash and part must be lvalues.
#define MIX(hash, part) \
{ \
(part) *= kStringHashM; \
(part) ^= (part) >> kStringHashR; \
(part) *= kStringHashM; \
(hash) *= kStringHashM; \
(hash) ^= (part); \
}
uint32_t Utils::StringHash(const void* data, int length) {
int size = length;
uint32_t hash = size;
auto cursor = reinterpret_cast<const uint8_t*>(data);
if (size >= kInt32Size) {
const intptr_t misalignment =
reinterpret_cast<intptr_t>(cursor) % kInt32Size;
if (misalignment > 0) {
// Stores 4-byte values starting from the start of the string to mimic
// the algorithm on aligned data.
uint32_t data_window = 0;
// Shift sizes for adjusting the data window when adding the next aligned
// piece of data.
const uint32_t sr = misalignment * kBitsPerByte;
const uint32_t sl = kBitsPerInt32 - sr;
const intptr_t pre_alignment_length = kInt32Size - misalignment;
switch (pre_alignment_length) {
case 3:
data_window |= cursor[2] << 16;
FALL_THROUGH;
case 2:
data_window |= cursor[1] << 8;
FALL_THROUGH;
case 1:
data_window |= cursor[0];
}
cursor += pre_alignment_length;
size -= pre_alignment_length;
// Mix four bytes at a time now that we're at an aligned spot.
for (; size >= kInt32Size; cursor += kInt32Size, size -= kInt32Size) {
uint32_t aligned_part = *reinterpret_cast<const uint32_t*>(cursor);
data_window |= (aligned_part << sl);
MIX(hash, data_window);
data_window = aligned_part >> sr;
}
if (size >= misalignment) {
// There's one more full window in the data. We'll let the normal tail
// code handle any partial window.
switch (misalignment) {
case 3:
data_window |= cursor[2] << (16 + sl);
FALL_THROUGH;
case 2:
data_window |= cursor[1] << (8 + sl);
FALL_THROUGH;
case 1:
data_window |= cursor[0] << sl;
}
MIX(hash, data_window);
cursor += misalignment;
size -= misalignment;
} else {
// This is a partial window, so just xor and multiply by M.
switch (size) {
case 2:
data_window |= cursor[1] << (8 + sl);
FALL_THROUGH;
case 1:
data_window |= cursor[0] << sl;
}
hash ^= data_window;
hash *= kStringHashM;
cursor += size;
size = 0;
}
} else {
// Mix four bytes at a time into the hash.
for (; size >= kInt32Size; size -= kInt32Size, cursor += kInt32Size) {
uint32_t part = *reinterpret_cast<const uint32_t*>(cursor);
MIX(hash, part);
}
}
}
// Handle the last few bytes of the string if any.
switch (size) {
case 3:
hash ^= cursor[2] << 16;
FALL_THROUGH;
case 2:
hash ^= cursor[1] << 8;
FALL_THROUGH;
case 1:
hash ^= cursor[0];
hash *= kStringHashM;
}
// Do a few final mixes of the hash to ensure the last few bytes are
// well-incorporated.
hash ^= hash >> 13;
hash *= kStringHashM;
hash ^= hash >> 15;
return hash;
}
#undef MIX
uint32_t Utils::WordHash(intptr_t key) {
// TODO(iposva): Need to check hash spreading.
// This example is from http://www.concentric.net/~Ttwang/tech/inthash.htm
// via. http://web.archive.org/web/20071223173210/http://www.concentric.net/~Ttwang/tech/inthash.htm
uword a = static_cast<uword>(key);
a = (a + 0x7ed55d16) + (a << 12);
a = (a ^ 0xc761c23c) ^ (a >> 19);
a = (a + 0x165667b1) + (a << 5);
a = (a + 0xd3a2646c) ^ (a << 9);
a = (a + 0xfd7046c5) + (a << 3);
a = (a ^ 0xb55a4f09) ^ (a >> 16);
return static_cast<uint32_t>(a);
}
char* Utils::SCreate(const char* format, ...) {
va_list args;
va_start(args, format);
char* buffer = VSCreate(format, args);
va_end(args);
return buffer;
}
char* Utils::VSCreate(const char* format, va_list args) {
// Measure.
va_list measure_args;
va_copy(measure_args, args);
intptr_t len = VSNPrint(NULL, 0, format, measure_args);
va_end(measure_args);
char* buffer = reinterpret_cast<char*>(malloc(len + 1));
ASSERT(buffer != NULL);
// Print.
va_list print_args;
va_copy(print_args, args);
VSNPrint(buffer, len + 1, format, print_args);
va_end(print_args);
return buffer;
}
Utils::CStringUniquePtr Utils::CreateCStringUniquePtr(char* str) {
return std::unique_ptr<char, decltype(std::free)*>{str, std::free};
}
static void GetLastErrorAsString(char** error) {
if (error == nullptr) return; // Nothing to do.
#if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_MACOS) || \
defined(DART_HOST_OS_ANDROID) || defined(DART_HOST_OS_FUCHSIA)
const char* status = dlerror();
*error = status != nullptr ? strdup(status) : nullptr;
#elif defined(DART_HOST_OS_WINDOWS)
const int status = GetLastError();
*error = status != 0 ? Utils::SCreate("error code %i", status) : nullptr;
#else
*error = Utils::StrDup("loading dynamic libraries is not supported");
#endif
}
void* Utils::LoadDynamicLibrary(const char* library_path, char** error) {
void* handle = nullptr;
#if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_MACOS) || \
defined(DART_HOST_OS_ANDROID) || defined(DART_HOST_OS_FUCHSIA)
handle = dlopen(library_path, RTLD_LAZY);
#elif defined(DART_HOST_OS_WINDOWS)
SetLastError(0); // Clear any errors.
if (library_path == nullptr) {
handle = GetModuleHandle(nullptr);
} else {
// Convert to wchar_t string.
const int name_len = MultiByteToWideChar(
CP_UTF8, /*dwFlags=*/0, library_path, /*cbMultiByte=*/-1, nullptr, 0);
if (name_len != 0) {
std::unique_ptr<wchar_t[]> name(new wchar_t[name_len]);
const int written_len =
MultiByteToWideChar(CP_UTF8, /*dwFlags=*/0, library_path,
/*cbMultiByte=*/-1, name.get(), name_len);
RELEASE_ASSERT(written_len == name_len);
handle = LoadLibraryW(name.get());
}
}
#endif
if (handle == nullptr) {
GetLastErrorAsString(error);
}
return handle;
}
void* Utils::ResolveSymbolInDynamicLibrary(void* library_handle,
const char* symbol,
char** error) {
void* result = nullptr;
#if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_MACOS) || \
defined(DART_HOST_OS_ANDROID) || defined(DART_HOST_OS_FUCHSIA)
dlerror(); // Clear any errors.
result = dlsym(library_handle, symbol);
// Note: nullptr might be a valid return from dlsym. Must call dlerror
// to differentiate.
GetLastErrorAsString(error);
return result;
#elif defined(DART_HOST_OS_WINDOWS)
SetLastError(0);
result = reinterpret_cast<void*>(
GetProcAddress(reinterpret_cast<HMODULE>(library_handle), symbol));
#endif
if (result == nullptr) {
GetLastErrorAsString(error);
}
return result;
}
void Utils::UnloadDynamicLibrary(void* library_handle, char** error) {
bool ok = false;
#if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_MACOS) || \
defined(DART_HOST_OS_ANDROID) || defined(DART_HOST_OS_FUCHSIA)
ok = dlclose(library_handle) == 0;
#elif defined(DART_HOST_OS_WINDOWS)
SetLastError(0); // Clear any errors.
ok = FreeLibrary(reinterpret_cast<HMODULE>(library_handle));
#endif
if (!ok) {
GetLastErrorAsString(error);
}
}
} // namespace dart