903eea6bfb
The same bundling that is used for `dart build` is now also used for `dart test` and `dart run`, except that the output directory is `.dart_tool/native_assets`. This way all native code assets are placed next to each other in the `lib` directory, and loaded from there instead of loading them in place from where the build/link hooks placed them. By standardizing on this layout the different modes of running dart code that support native assets can use the same mechanisms to support dynamic linking between libraries. On macOS, install names of dylibs are rewritten to support dynamic linking, similar to the changes in https://github.com/flutter/flutter/pull/153054. On Windows, loading of DLLs is altered so that the directory of the DLL that is being loaded is considered when loading dependent DLLs. Tests are added to verify that dynamic linking works as expected. TEST=pkg/dartdev/test/native_assets/{build,run,test}_test.dart R=mosum@google.com Related: https://github.com/dart-lang/native/issues/190 Fixes: https://github.com/dart-lang/sdk/issues/56459 Change-Id: Ie4a41e5b7382ab1cea39e93d29d085bf9986828b Cq-Include-Trybots: luci.dart.try:pkg-linux-debug-try,pkg-linux-release-arm64-try,pkg-linux-release-try,pkg-mac-release-arm64-try,pkg-mac-release-try,pkg-win-release-arm64-try,pkg-win-release-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381580 Reviewed-by: Moritz Sümmermann <mosum@google.com> Commit-Queue: Daco Harkes <dacoharkes@google.com> Reviewed-by: Daco Harkes <dacoharkes@google.com>
373 lines
12 KiB
C++
373 lines
12 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 {
|
|
|
|
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.Montgomery'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(nullptr, 0, format, measure_args);
|
|
va_end(measure_args);
|
|
|
|
char* buffer = reinterpret_cast<char*>(malloc(len + 1));
|
|
ASSERT(buffer != nullptr);
|
|
|
|
// Print.
|
|
va_list print_args;
|
|
va_copy(print_args, args);
|
|
VSNPrint(buffer, len + 1, format, print_args);
|
|
va_end(print_args);
|
|
return buffer;
|
|
}
|
|
|
|
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();
|
|
if (status != 0) {
|
|
char* description = nullptr;
|
|
int length = FormatMessageA(
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
nullptr, status, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
|
|
reinterpret_cast<char*>(&description), 0, nullptr);
|
|
if (length == 0) {
|
|
// Seems like there is no message for this error code.
|
|
*error = Utils::SCreate("error code %i", status);
|
|
} else {
|
|
*error = Utils::SCreate("%s (error code: %i)", description, status);
|
|
}
|
|
|
|
LocalFree(description);
|
|
} else {
|
|
*error = nullptr;
|
|
}
|
|
#else
|
|
*error = Utils::StrDup("loading dynamic libraries is not supported");
|
|
#endif
|
|
}
|
|
|
|
void* Utils::LoadDynamicLibrary(const char* library_path,
|
|
bool search_dll_load_dir,
|
|
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);
|
|
if (search_dll_load_dir) {
|
|
handle =
|
|
LoadLibraryExW(name.get(), NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
|
|
} else {
|
|
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
|