Files
Ryan Macnak d36adbacaf [vm] Remove the VM isolate.
The former contents of the VM isolate are now included into each isolate group. This makes each isolate group's heap independent, and in particular allows each heap to be allocated to a separate pointer cage (not done in this CL).

The duplicated stubs that allowed PC relative calls are removed, since the originals can now be the target of PC relative calls.

The bootstrapping needing to load an AppJIT or AppAOT snapshot is reduced to allocating the oddballs. The code is entirely dropped in the AOT runtime, but the JIT runtime still has it to allow for flags to affect the compilation of the stub code. Further refactoring might be able to remove this for the JIT runtime too, with only gen_snapshot knowing how to bootstrap.

Class serialization no longer distinguishes predefined classes.

The page containing null is marked as never-evacuate. null, false and true must not move because the compiler relies on their low bits having certain patterns for some optimizations. (Previously, the entire VM isolate heap never moved.)

Compaction is disabled for IA32. Due to register pressure, some stub calls must not use a scratch register and embed the address of Code.

The page containing the call-through-safepoint stub is frozen when running with --write-protect-code and the stub is created at runtime (instead of loaded from an AppJIT or AppAOT snapshot). This stub must remain executable even during a safepoint, as a foreign call might during return during a safepoint and only block after the stub directs it to the runtime.

The snapshot symbols are renamed to kDartSnapshotData and kDartSnapshotText. There is no need to distinguish the VM isolate's snapshot, and snaphots are per isolate group not per isolate. Aliases with the old names are added to ease migration.

Some global flags that were automatically set based on the VM isolate's snapshot are now isolate group flags and automatically set by the isolate group's snapshot.

TEST=ci
Change-Id: Iee82016057d609112e9b021d178fc3d4d18b5044
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/500621
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Tess Strickland <sstrickl@google.com>
SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com>
Commit-Queue: Ryan Macnak <rmacnak@google.com>
2026-05-18 11:35:03 -07:00

620 lines
21 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.
#ifndef RUNTIME_PLATFORM_UTILS_H_
#define RUNTIME_PLATFORM_UTILS_H_
#include <bit>
#include <cstdlib>
#include <limits>
#include <memory>
#include <type_traits>
#include "platform/assert.h"
#include "platform/globals.h"
#include "platform/undefined_behavior_sanitizer.h"
namespace dart {
template <typename T>
class CAllocUniquePtr : public std::unique_ptr<T, decltype(std::free)*> {
public:
CAllocUniquePtr()
: std::unique_ptr<T, decltype(std::free)*>(nullptr, std::free) {}
explicit CAllocUniquePtr(T* value)
: std::unique_ptr<T, decltype(std::free)*>(value, std::free) {}
CAllocUniquePtr& operator=(std::nullptr_t value) {
std::unique_ptr<T, decltype(std::free)*>::operator=(value);
return *this;
}
};
using CStringUniquePtr = CAllocUniquePtr<char>;
class Utils {
public:
template <typename T>
static inline T Minimum(T x, T y) {
return x < y ? x : y;
}
template <typename T>
static constexpr inline T Maximum(T x, T y) {
return x > y ? x : y;
}
// Calculates absolute value of a given signed integer.
// `x` must not be equal to minimum value representable by `T`
// as its absolute value is out of range.
template <typename T>
static inline T Abs(T x) {
// Note: as a general rule, it is not OK to use STL in Dart VM.
// However, std::numeric_limits<T>::min() and max() are harmless
// and worthwhile exception from this rule.
ASSERT(x != std::numeric_limits<T>::min());
if (x < 0) return -x;
return x;
}
// Calculates absolute value of a given signed integer with saturation.
// If `x` equals to minimum value representable by `T`, then
// absolute value is saturated to the maximum value representable by `T`.
template <typename T>
static inline T AbsWithSaturation(T x) {
if (x < 0) {
// Note: as a general rule, it is not OK to use STL in Dart VM.
// However, std::numeric_limits<T>::min() and max() are harmless
// and worthwhile exception from this rule.
if (x == std::numeric_limits<T>::min()) {
return std::numeric_limits<T>::max();
}
return -x;
}
return x;
}
template <typename T>
static constexpr bool IsPowerOfTwo(T x) {
using Unsigned = typename std::make_unsigned<T>::type;
return std::has_single_bit(static_cast<Unsigned>(x));
}
template <typename T>
static constexpr int ShiftForPowerOfTwo(T x) {
ASSERT(IsPowerOfTwo(x));
int num_shifts = 0;
while (x > 1) {
num_shifts++;
x = x >> 1;
}
return num_shifts;
}
template <typename T>
static constexpr bool IsAligned(T x,
uintptr_t alignment,
uintptr_t offset = 0) {
ASSERT(IsPowerOfTwo(alignment));
ASSERT(offset < alignment);
return (x & (alignment - 1)) == offset;
}
template <typename T>
static constexpr bool IsAligned(T* x,
uintptr_t alignment,
uintptr_t offset = 0) {
return IsAligned(reinterpret_cast<uword>(x), alignment, offset);
}
template <typename T>
static constexpr inline T RoundDown(T x, intptr_t alignment) {
ASSERT(IsPowerOfTwo(alignment));
return (x & -alignment);
}
template <typename T>
static inline T* RoundDown(T* x, intptr_t alignment) {
return reinterpret_cast<T*>(
RoundDown(reinterpret_cast<uword>(x), alignment));
}
template <typename T>
static constexpr inline T RoundUp(T x,
uintptr_t alignment,
uintptr_t offset = 0) {
ASSERT(offset < alignment);
return RoundDown(x + alignment - 1 + offset, alignment) - offset;
}
template <typename T>
static inline T* RoundUp(T* x, uintptr_t alignment, uintptr_t offset = 0) {
return reinterpret_cast<T*>(
RoundUp(reinterpret_cast<uword>(x), alignment, offset));
}
static constexpr uintptr_t RoundUpToPowerOfTwo(uintptr_t x) {
if (x == 0) return 0;
return std::bit_ceil(x);
}
static constexpr int CountOneBits64(uint64_t x) { return std::popcount(x); }
static constexpr int CountOneBits32(uint32_t x) { return std::popcount(x); }
static constexpr int CountOneBitsWord(uword x) { return std::popcount(x); }
template <typename T>
static constexpr int CountOneBits(T x) {
return std::popcount(x);
}
// TODO(koda): Compare to flsll call/intrinsic.
static constexpr size_t HighestBit(int64_t v) {
uint64_t x = static_cast<uint64_t>((v > 0) ? v : -v);
uint64_t t = 0;
size_t r = 0;
if ((t = x >> 32) != 0) {
x = t;
r += 32;
}
if ((t = x >> 16) != 0) {
x = t;
r += 16;
}
if ((t = x >> 8) != 0) {
x = t;
r += 8;
}
if ((t = x >> 4) != 0) {
x = t;
r += 4;
}
if ((t = x >> 2) != 0) {
x = t;
r += 2;
}
if (x > 1) r += 1;
return r;
}
static constexpr size_t BitLength(int64_t value) {
// Flip bits if negative (-1 becomes 0).
value ^= value >> (8 * sizeof(value) - 1);
return std::bit_width(static_cast<uint64_t>(value));
}
static int CountLeadingZeros32(uint32_t x) { return std::countl_zero(x); }
static int CountLeadingZeros64(uint64_t x) { return std::countl_zero(x); }
static int CountLeadingZerosWord(uword x) { return std::countl_zero(x); }
static int CountTrailingZeros32(uint32_t x) { return std::countr_zero(x); }
static int CountTrailingZeros64(uint64_t x) { return std::countr_zero(x); }
static int CountTrailingZerosWord(uword x) { return std::countr_zero(x); }
template <typename T>
static constexpr int CountLeadingOnes(T x) {
return std::countl_one(x);
}
static uint64_t ReverseBits64(uint64_t x);
static uint32_t ReverseBits32(uint32_t x);
static uword ReverseBitsWord(uword x) {
#ifdef ARCH_IS_64_BIT
return ReverseBits64(x);
#else
return ReverseBits32(x);
#endif
}
// Computes magic numbers to implement DIV or MOD operator.
static void CalculateMagicAndShiftForDivRem(int64_t divisor,
int64_t* magic,
int64_t* shift);
// Computes a hash value for the given series of bytes.
static uint32_t StringHash(const void* data, int length);
static uint64_t StringHash64(const void* data, int length);
// Computes a hash value for the given word.
static uint32_t WordHash(intptr_t key);
// Check whether an N-bit two's-complement representation can hold value.
template <typename T>
static inline bool IsInt(intptr_t N, T value) {
ASSERT(N >= 1);
constexpr intptr_t value_size_in_bits = kBitsPerByte * sizeof(T);
if constexpr (std::is_signed<T>::value) {
if (N >= value_size_in_bits) return true; // Trivially fits.
const T limit = static_cast<T>(1) << (N - 1);
return (-limit <= value) && (value < limit);
} else {
if (N > value_size_in_bits) return true; // Trivially fits.
const T limit = static_cast<T>(1) << (N - 1);
return value < limit;
}
}
template <typename T>
static inline bool IsUint(intptr_t N, T value) {
ASSERT(N >= 1);
constexpr intptr_t value_size_in_bits = kBitsPerByte * sizeof(T);
if constexpr (std::is_signed<T>::value) {
if (value < 0) return false; // Not an unsigned value.
if (N >= value_size_in_bits - 1) {
return true; // N can fit the magnitude bits.
}
} else {
if (N >= value_size_in_bits) return true; // Trivially fits.
}
const T limit = (static_cast<T>(1) << N) - 1;
return value <= limit;
}
// Check whether the magnitude of value fits in N bits. This differs from
// IsInt(N + 1, value) only in that this returns false for the minimum value
// of a N+1 bit two's complement value.
//
// Primarily used for testing whether a two's complement value can be used in
// a place where the sign is replaced with a marker that says whether the
// magnitude is added or subtracted, e.g., the U bit (bit 23) in some ARM7
// instructions.
template <typename T>
static inline bool MagnitudeIsUint(intptr_t N, T value) {
ASSERT(N >= 1);
if constexpr (std::is_signed<T>::value) {
using Unsigned = typename std::make_unsigned<T>::type;
if (value < 0) return IsUint<Unsigned>(N, -value);
}
return IsUint(N, value);
}
static inline int32_t Low16Bits(int32_t value) {
return static_cast<int32_t>(value & 0xffff);
}
static inline int32_t High16Bits(int32_t value) {
return static_cast<int32_t>(value >> 16);
}
static inline int32_t Low32Bits(int64_t value) {
return static_cast<int32_t>(value);
}
static inline int32_t High32Bits(int64_t value) {
return static_cast<int32_t>(value >> 32);
}
static inline int64_t LowHighTo64Bits(uint32_t low, int32_t high) {
return (static_cast<uint64_t>(high) << 32) | (low & 0x0ffffffffLL);
}
static inline constexpr bool IsAlphaNumeric(uint32_t c) {
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
IsDecimalDigit(c);
}
static inline constexpr bool IsDecimalDigit(uint32_t c) {
return ('0' <= c) && (c <= '9');
}
static bool IsHexDigit(char c) {
return IsDecimalDigit(c) || (('A' <= c) && (c <= 'F')) ||
(('a' <= c) && (c <= 'f'));
}
static int HexDigitToInt(char c) {
ASSERT(IsHexDigit(c));
if (IsDecimalDigit(c)) return c - '0';
if (('A' <= c) && (c <= 'F')) return 10 + (c - 'A');
return 10 + (c - 'a');
}
static char IntToHexDigit(int i) {
ASSERT(0 <= i && i < 16);
if (i < 10) return static_cast<char>('0' + i);
return static_cast<char>('A' + (i - 10));
}
// Perform a range check, checking if
// offset + count <= length
// without the risk of integer overflow.
static inline bool RangeCheck(intptr_t offset,
intptr_t count,
intptr_t length) {
return offset >= 0 && count >= 0 && length >= 0 &&
count <= (length - offset);
}
static inline bool WillAddOverflow(int64_t a, int64_t b) {
return ((b > 0) && (a > (kMaxInt64 - b))) ||
((b < 0) && (a < (kMinInt64 - b)));
}
static inline bool WillSubOverflow(int64_t a, int64_t b) {
return ((b > 0) && (a < (kMinInt64 + b))) ||
((b < 0) && (a > (kMaxInt64 + b)));
}
// Adds two int64_t values with wrapping around
// (two's complement arithmetic).
template <typename T = int64_t>
static inline T AddWithWrapAround(T a, T b) {
// Avoid undefined behavior by doing arithmetic in the unsigned type.
using Unsigned = typename std::make_unsigned<T>::type;
return static_cast<T>(static_cast<Unsigned>(a) + static_cast<Unsigned>(b));
}
// Subtracts two int64_t values with wrapping around
// (two's complement arithmetic).
template <typename T = int64_t>
static inline T SubWithWrapAround(T a, T b) {
// Avoid undefined behavior by doing arithmetic in the unsigned type.
using Unsigned = typename std::make_unsigned<T>::type;
return static_cast<T>(static_cast<Unsigned>(a) - static_cast<Unsigned>(b));
}
// Multiplies two int64_t values with wrapping around
// (two's complement arithmetic).
template <typename T = int64_t>
static inline T MulWithWrapAround(T a, T b) {
// Avoid undefined behavior by doing arithmetic in the unsigned type.
using Unsigned = typename std::make_unsigned<T>::type;
return static_cast<T>(static_cast<Unsigned>(a) * static_cast<Unsigned>(b));
}
template <typename T = int64_t>
static inline T NegWithWrapAround(T a) {
// Avoid undefined behavior by doing arithmetic in the unsigned type.
using Unsigned = typename std::make_unsigned<T>::type;
return static_cast<T>(-static_cast<Unsigned>(a));
}
// Shifts int64_t value left. Supports any non-negative number of bits and
// silently discards shifted out bits.
static inline int64_t ShiftLeftWithTruncation(int64_t a, int64_t b) {
ASSERT(b >= 0);
if (b >= kBitsPerInt64) {
return 0;
}
// Avoid undefined behavior by doing arithmetic in the unsigned type.
return static_cast<int64_t>(static_cast<uint64_t>(a) << b);
}
template <typename T>
static inline T RotateLeft(T value, uint8_t rotate) {
const uint8_t width = sizeof(T) * kBitsPerByte;
ASSERT(0 <= rotate);
ASSERT(rotate <= width);
using Unsigned = typename std::make_unsigned<T>::type;
return std::rotl(static_cast<Unsigned>(value), rotate);
}
template <typename T>
static inline T RotateRight(T value, uint8_t rotate) {
const uint8_t width = sizeof(T) * kBitsPerByte;
ASSERT(0 <= rotate);
ASSERT(rotate <= width);
using Unsigned = typename std::make_unsigned<T>::type;
return std::rotr(static_cast<Unsigned>(value), rotate);
}
NO_SANITIZE_UNDEFINED("float-divide-by-zero")
static inline float DivideAllowZero(float a, float b) { return a / b; }
NO_SANITIZE_UNDEFINED("float-divide-by-zero")
static inline double DivideAllowZero(double a, double b) { return a / b; }
// Utility functions for converting values from host endianness to
// big or little endian values.
static uint16_t HostToBigEndian16(uint16_t host_value);
static uint32_t HostToBigEndian32(uint32_t host_value);
static uint64_t HostToBigEndian64(uint64_t host_value);
static uint16_t HostToLittleEndian16(uint16_t host_value);
static uint32_t HostToLittleEndian32(uint32_t host_value);
static uint64_t HostToLittleEndian64(uint64_t host_value);
// Going between Host <-> LE/BE is the same operation for all practical
// purposes.
static inline uint32_t BigEndianToHost32(uint32_t be_value) {
return HostToBigEndian32(be_value);
}
static inline uint64_t LittleEndianToHost64(uint64_t le_value) {
return HostToLittleEndian64(le_value);
}
static bool DoublesBitEqual(const double a, const double b) {
return bit_cast<int64_t, double>(a) == bit_cast<int64_t, double>(b);
}
// A double-to-integer conversion that avoids undefined behavior.
// Out of range values and NaNs are converted to minimum value
// for type T.
template <typename T>
static T SafeDoubleToInt(double v) {
const double min = static_cast<double>(std::numeric_limits<T>::min());
const double max = static_cast<double>(std::numeric_limits<T>::max());
return (min <= v && v <= max) ? static_cast<T>(v)
: std::numeric_limits<T>::min();
}
// dart2js represents integers as double precision floats, which can
// represent anything in the range -2^53 ... 2^53.
static bool IsJavaScriptInt(int64_t value) {
return ((-0x20000000000000LL <= value) && (value <= 0x20000000000000LL));
}
// The lowest n bits are 1, the others are 0.
template <typename T = uword>
static constexpr T NBitMask(size_t n) {
using Unsigned = typename std::make_unsigned<T>::type;
constexpr size_t kBitsPerT = sizeof(T) * kBitsPerByte;
assert(n <= sizeof(T) * kBitsPerT);
return static_cast<T>(n == kBitsPerT ? std::numeric_limits<Unsigned>::max()
: (static_cast<Unsigned>(1) << n) - 1);
}
template <typename T = uword>
static constexpr T Bit(size_t n) {
ASSERT(n < sizeof(T) * kBitsPerByte);
T bit = 1;
return bit << n;
}
template <typename T>
static constexpr bool TestBit(T mask, size_t position) {
ASSERT(position < sizeof(T) * kBitsPerByte);
return ((mask >> position) & 1) != 0;
}
template <typename T>
class BitsIterator {
public:
explicit BitsIterator(uint32_t bits) : bits_(bits), bit_(bits & -bits) {}
DART_FORCE_INLINE T operator*() const {
return static_cast<T>(BitPosition(bit_));
}
DART_FORCE_INLINE bool operator==(const BitsIterator& other) const {
return bits_ == other.bits_ && bit_ == other.bit_;
}
DART_FORCE_INLINE bool operator!=(const BitsIterator& other) const {
return !(*this == other);
}
DART_FORCE_INLINE BitsIterator& operator++() {
bits_ ^= bit_;
bit_ = bits_ & -bits_;
return *this;
}
private:
// Returns position of the given bit. Unlike CountTrailingZeroes assumes
// that bit is not zero without checking!
static DART_FORCE_INLINE intptr_t BitPosition(uint32_t bit) {
#if defined(DART_HOST_OS_WINDOWS)
unsigned long position; // NOLINT
BitScanForward(&position, bit);
return static_cast<int>(position);
#else
return __builtin_ctz(bit);
#endif
}
uint32_t bits_;
intptr_t bit_;
};
template <typename T>
class BitsRange {
public:
explicit BitsRange(uint32_t bits) : bits_(bits) {}
BitsIterator<T> begin() { return BitsIterator<T>(bits_); }
BitsIterator<T> end() { return BitsIterator<T>(0); }
public:
const uint32_t bits_;
};
static char* StrError(int err, char* buffer, size_t bufsize);
// Not all platforms support strndup.
static char* StrNDup(const char* s, intptr_t n);
static char* StrDup(const char* s);
static intptr_t StrNLen(const char* s, intptr_t n);
static bool StrStartsWith(const char* s, const char* prefix) {
return strncmp(s, prefix, strlen(prefix)) == 0;
}
static int Close(int fildes);
static size_t Read(int filedes, void* buf, size_t nbyte);
static int Unlink(const char* path);
// Print formatted output info a buffer.
//
// Does not write more than size characters (including the trailing '\0').
//
// Returns the number of characters (excluding the trailing '\0')
// that would been written if the buffer had been big enough. If
// the return value is greater or equal than the given size then the
// output has been truncated. The return value is never negative.
//
// The buffer will always be terminated by a '\0', unless the buffer
// is of size 0. The buffer might be nullptr if the size is 0.
//
// This specification conforms to C99 standard which is implemented
// by glibc 2.1+ with one exception: the C99 standard allows a
// negative return value. We will terminate the vm rather than let
// that occur.
static int SNPrint(char* str, size_t size, const char* format, ...)
PRINTF_ATTRIBUTE(3, 4);
static int VSNPrint(char* str, size_t size, const char* format, va_list args);
// Allocate a string and print formatted output into a malloc'd buffer.
static char* SCreate(const char* format, ...) PRINTF_ATTRIBUTE(1, 2);
static char* VSCreate(const char* format, va_list args);
// Load dynamic library from the given |library_path| and return the
// library handle. |library_path| can be |nullptr| in which case
// library handle representing the executable is returned.
// If an error occurs returns |nullptr| and populates
// |error| (if provided) with an error message (caller must free this message
// when it is no longer needed).
static void* LoadDynamicLibrary(const char* library_path,
bool search_dll_load_dir = false,
char** error = nullptr);
static void* LoadDynamicLibrary(const char* library_path,
char** error = nullptr) {
return LoadDynamicLibrary(library_path, /*search_dll_load_dir=*/false,
error);
}
// Resolve the given |symbol| within the library referenced by the
// given |library_handle|.
// If an error occurs populates |error| (if provided) with an error message
// (caller must free this message when it is no longer needed).
// Note: on some platforms |nullptr| is a valid value for a symbol, so to
// check if resolution succeeded one must instead provide non-null |error|
// and then check if it was populated with an error message.
static void* ResolveSymbolInDynamicLibrary(void* library_handle,
const char* symbol,
char** error = nullptr);
// Unload the library referenced by the given |library_handle|.
// If an error occurs returns |nullptr| and populates
// |error| (if provided) with an error message (caller must free this message
// when it is no longer needed).
static void UnloadDynamicLibrary(void* library_handle,
char** error = nullptr);
// Returns the basename of the given path. The returned string is malloced
// and must be freed by the caller once no longer needed.
//
// If path is nullptr, returns nullptr.
//
// Returns nullptr if the operating system does not support this operation.
static char* Basename(const char* path);
#if defined(DART_HOST_OS_LINUX)
static bool IsWindowsSubsystemForLinux();
#endif
};
} // namespace dart
#if defined(DART_HOST_OS_ANDROID)
#include "platform/utils_android.h"
#elif defined(DART_HOST_OS_FUCHSIA)
#include "platform/utils_fuchsia.h"
#elif defined(DART_HOST_OS_LINUX)
#include "platform/utils_linux.h"
#elif defined(DART_HOST_OS_MACOS)
#include "platform/utils_macos.h"
#elif defined(DART_HOST_OS_WINDOWS)
#include "platform/utils_win.h"
#else
#error Unknown target os.
#endif
#endif // RUNTIME_PLATFORM_UTILS_H_