45a46ca2b8
Unlikecfc8e6de, this does _not_ replace the default variable length encoding for {Read,Write}Streams, but insteads adds separate {Read,Write}{S,}LEB128 methods to the appropriate classes. If we later find the cause of the issues that led to the revert ofcfc8e6de, it'll be easy to switch over then. Note that WriteLEB128 asserts that the value is non-negative if used with a signed type (since negative values suggests that SLEB128 should be used instead for minimal encoding). Also removes the various other encoding and decoding methods for (S)LEB128 across the codebase and changes those clients to use {Read,Write}Streams instead. Other cleanups: * Various constant-related cleanups in datastream.h. * Adds DART_FORCE_INLINE to ReadStream::ReadByte and uses it in the default variable length decoding methods for retrieving bytes from the stream instead of managing current_ by hand. * Creates a canonical empty CompressedStackMaps instance and uses that instead of the null CompressedStackMaps instance in most cases. The only remaining (expected) use of the null CompressedStackMaps instance is for the global table in the object store when no global table exists (e.g., in JIT mode before any snapshotting). * Moves CompressedStackMapsIterator from code_descriptors.h to an Iterator class within CompressedStackMaps in object.h (similar to PcDescriptors::Iterator), to limit friend declarations and because it conceptually makes more sense as part of CompressedStackMaps. * Removed CompressedStackMaps::PayloadByte, since existing clients (CompressedStackMaps::Iterator, StackMapEntry in program_visitor.cc) are better served by just operating on the payload buffer directly (with appropriate NoSafepointScopes). * WriteStreams no longer allocate their initial space on construction, but rather on the first write, so no allocation is performed by constructing a never-used WriteStream. Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-linux-debug-x64-try,vm-kernel-precomp-linux-debug-simarm_x64-try,vm-kernel-precomp-mac-release-simarm64-try,vm-kernel-mac-debug-x64-try,vm-kernel-win-debug-x64-try,vm-kernel-win-debug-ia32-try,vm-kernel-precomp-win-release-x64-try,vm-kernel-ubsan-linux-release-x64-try,vm-kernel-tsan-linux-release-x64-try,vm-kernel-precomp-ubsan-linux-release-x64-try,vm-kernel-precomp-tsan-linux-release-x64-try,vm-kernel-precomp-msan-linux-release-x64-try,vm-kernel-precomp-asan-linux-release-x64-try,vm-kernel-msan-linux-release-x64-try,vm-kernel-asan-linux-release-x64-try Change-Id: Ice63321abaa79157fbe9f230a864c8bba0e6dea9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/166421 Reviewed-by: Ryan Macnak <rmacnak@google.com> Commit-Queue: Tess Strickland <sstrickl@google.com>
659 lines
21 KiB
C++
659 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_VM_DATASTREAM_H_
|
|
#define RUNTIME_VM_DATASTREAM_H_
|
|
|
|
#include "platform/assert.h"
|
|
#include "platform/utils.h"
|
|
#include "vm/allocation.h"
|
|
#include "vm/exceptions.h"
|
|
#include "vm/globals.h"
|
|
#include "vm/os.h"
|
|
#include "vm/zone.h"
|
|
|
|
namespace dart {
|
|
|
|
static const int8_t kDataBitsPerByte = 7;
|
|
static const int8_t kByteMask = (1 << kDataBitsPerByte) - 1;
|
|
static const int8_t kMaxUnsignedDataPerByte = kByteMask;
|
|
static const int8_t kMinDataPerByte = -(1 << (kDataBitsPerByte - 1));
|
|
static const int8_t kMaxDataPerByte = (~kMinDataPerByte & kByteMask); // NOLINT
|
|
static const uint8_t kEndByteMarker = (255 - kMaxDataPerByte);
|
|
static const uint8_t kEndUnsignedByteMarker = (255 - kMaxUnsignedDataPerByte);
|
|
|
|
struct LEB128Constants : AllStatic {
|
|
// Convenience template for ensuring non-signed types trigger SFINAE.
|
|
template <typename T, typename S>
|
|
using only_if_signed =
|
|
typename std::enable_if<std::is_signed<T>::value, S>::type;
|
|
|
|
// Convenience template for ensuring signed types trigger SFINAE.
|
|
template <typename T, typename S>
|
|
using only_if_unsigned =
|
|
typename std::enable_if<std::is_unsigned<T>::value, S>::type;
|
|
|
|
// (S)LEB128 encodes 7 bits of data per byte (hence 128).
|
|
static constexpr uint8_t kDataBitsPerByte = 7;
|
|
static constexpr uint8_t kDataByteMask = (1 << kDataBitsPerByte) - 1;
|
|
// If more data follows a given data byte, the high bit is set.
|
|
static constexpr uint8_t kMoreDataMask = (1 << kDataBitsPerByte);
|
|
// For SLEB128, the high bit in the data of the last byte is the sign bit.
|
|
static constexpr uint8_t kSignMask = (1 << (kDataBitsPerByte - 1));
|
|
};
|
|
|
|
class NonStreamingWriteStream;
|
|
|
|
// Stream for reading various types from a buffer.
|
|
class ReadStream : public ValueObject {
|
|
public:
|
|
ReadStream(const uint8_t* buffer, intptr_t size)
|
|
: buffer_(buffer), current_(buffer), end_(buffer + size) {}
|
|
|
|
// Creates a ReadStream that starts at a given position in the buffer.
|
|
ReadStream(const uint8_t* buffer, intptr_t size, intptr_t pos)
|
|
: ReadStream(buffer, size) {
|
|
SetPosition(pos);
|
|
}
|
|
|
|
template <int N, typename T>
|
|
class Raw {};
|
|
|
|
template <typename T>
|
|
class Raw<1, T> {
|
|
public:
|
|
static T Read(ReadStream* st) { return bit_cast<T>(st->ReadByte()); }
|
|
};
|
|
|
|
template <typename T>
|
|
class Raw<2, T> {
|
|
public:
|
|
static T Read(ReadStream* st) { return bit_cast<T>(st->Read16()); }
|
|
};
|
|
|
|
template <typename T>
|
|
class Raw<4, T> {
|
|
public:
|
|
static T Read(ReadStream* st) { return bit_cast<T>(st->Read32()); }
|
|
};
|
|
|
|
template <typename T>
|
|
class Raw<8, T> {
|
|
public:
|
|
static T Read(ReadStream* st) { return bit_cast<T>(st->Read64()); }
|
|
};
|
|
|
|
// Reads 'len' bytes from the stream.
|
|
void ReadBytes(uint8_t* addr, intptr_t len) {
|
|
ASSERT((end_ - current_) >= len);
|
|
if (len != 0) {
|
|
memmove(addr, current_, len);
|
|
}
|
|
current_ += len;
|
|
}
|
|
|
|
template <typename T = intptr_t>
|
|
T ReadUnsigned() {
|
|
return Read<T>(kEndUnsignedByteMarker);
|
|
}
|
|
|
|
intptr_t Position() const { return current_ - buffer_; }
|
|
void SetPosition(intptr_t value) {
|
|
ASSERT((end_ - buffer_) >= value);
|
|
current_ = buffer_ + value;
|
|
}
|
|
|
|
void Align(intptr_t alignment) {
|
|
intptr_t position_before = Position();
|
|
intptr_t position_after = Utils::RoundUp(position_before, alignment);
|
|
Advance(position_after - position_before);
|
|
}
|
|
|
|
const uint8_t* AddressOfCurrentPosition() const { return current_; }
|
|
|
|
void Advance(intptr_t value) {
|
|
ASSERT((end_ - current_) >= value);
|
|
current_ = current_ + value;
|
|
}
|
|
|
|
intptr_t PendingBytes() const {
|
|
ASSERT(end_ >= current_);
|
|
return (end_ - current_);
|
|
}
|
|
|
|
template <typename T>
|
|
T Read() {
|
|
return Read<T>(kEndByteMarker);
|
|
}
|
|
|
|
uword ReadWordWith32BitReads() {
|
|
constexpr intptr_t kNumRead32PerWord = kBitsPerWord / kBitsPerInt32;
|
|
|
|
uword value = 0;
|
|
for (intptr_t j = 0; j < kNumRead32PerWord; j++) {
|
|
const auto partial_value = Raw<kInt32Size, uint32_t>::Read(this);
|
|
value |= (static_cast<uword>(partial_value) << (j * kBitsPerInt32));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
private:
|
|
using C = LEB128Constants;
|
|
|
|
public:
|
|
template <typename T = uintptr_t>
|
|
C::only_if_unsigned<T, T> ReadLEB128() {
|
|
constexpr intptr_t kBitsPerT = kBitsPerByte * sizeof(T);
|
|
T r = 0;
|
|
uint8_t s = 0;
|
|
uint8_t b;
|
|
do {
|
|
ASSERT(s < kBitsPerT);
|
|
b = ReadByte();
|
|
r |= static_cast<T>(b & C::kDataByteMask) << s;
|
|
s += C::kDataBitsPerByte;
|
|
} while ((b & C::kMoreDataMask) != 0);
|
|
ASSERT(s < C::kDataBitsPerByte + kBitsPerT);
|
|
return r;
|
|
}
|
|
|
|
template <typename T>
|
|
C::only_if_signed<T, T> ReadLEB128() {
|
|
return bit_cast<T>(ReadLEB128<typename std::make_unsigned<T>::type>());
|
|
}
|
|
|
|
template <typename T>
|
|
C::only_if_unsigned<T, T> ReadSLEB128() {
|
|
constexpr intptr_t kBitsPerT = kBitsPerByte * sizeof(T);
|
|
T r = 0;
|
|
uint8_t s = 0;
|
|
uint8_t b;
|
|
do {
|
|
ASSERT(s < kBitsPerT);
|
|
b = ReadByte();
|
|
r |= static_cast<T>(b & C::kDataByteMask) << s;
|
|
s += C::kDataBitsPerByte;
|
|
} while ((b & C::kMoreDataMask) != 0);
|
|
ASSERT(s < C::kDataBitsPerByte + kBitsPerT);
|
|
// At this point, [s] contains how many data bits have made it into the
|
|
// value. If the value is negative and the count of data bits is less than
|
|
// the size of the value, then we need to extend the sign by setting the
|
|
// remaining (unset) most significant bits (MSBs).
|
|
T sign_bits = 0;
|
|
if ((b & C::kSignMask) != 0 && s < kBitsPerT) {
|
|
// Create a bitmask for the current data bits and invert it.
|
|
sign_bits = ~((static_cast<T>(1) << s) - 1);
|
|
}
|
|
return r | sign_bits;
|
|
}
|
|
|
|
template <typename T = intptr_t>
|
|
C::only_if_signed<T, T> ReadSLEB128() {
|
|
return bit_cast<T>(ReadSLEB128<typename std::make_unsigned<T>::type>());
|
|
}
|
|
|
|
private:
|
|
uint16_t Read16() { return Read16(kEndByteMarker); }
|
|
|
|
uint32_t Read32() { return Read32(kEndByteMarker); }
|
|
|
|
uint64_t Read64() { return Read64(kEndByteMarker); }
|
|
|
|
template <typename T>
|
|
T Read(uint8_t end_byte_marker) {
|
|
using Unsigned = typename std::make_unsigned<T>::type;
|
|
Unsigned b = ReadByte();
|
|
if (b > kMaxUnsignedDataPerByte) {
|
|
return b - end_byte_marker;
|
|
}
|
|
T r = 0;
|
|
uint8_t s = 0;
|
|
do {
|
|
r |= static_cast<Unsigned>(b) << s;
|
|
s += kDataBitsPerByte;
|
|
b = ReadByte();
|
|
} while (b <= kMaxUnsignedDataPerByte);
|
|
return r | (static_cast<Unsigned>(b - end_byte_marker) << s);
|
|
}
|
|
|
|
// Setting up needed variables for the unrolled loop sections below.
|
|
#define UNROLLED_INIT() \
|
|
using Unsigned = typename std::make_unsigned<T>::type; \
|
|
Unsigned b = ReadByte(); \
|
|
if (b > kMaxUnsignedDataPerByte) { \
|
|
return b - end_byte_marker; \
|
|
} \
|
|
T r = b;
|
|
|
|
// Part of the unrolled loop where the loop may stop, having read the last part,
|
|
// or continue reading.
|
|
#define UNROLLED_BODY(bit_start) \
|
|
static_assert(bit_start % kDataBitsPerByte == 0, \
|
|
"Bit start must be a multiple of the data bits per byte"); \
|
|
static_assert(bit_start >= 0 && bit_start < kBitsPerByte * sizeof(T), \
|
|
"Starting unrolled body at invalid bit position"); \
|
|
static_assert(bit_start + kDataBitsPerByte < kBitsPerByte * sizeof(T), \
|
|
"Unrolled body should not contain final bits in value"); \
|
|
b = ReadByte(); \
|
|
if (b > kMaxUnsignedDataPerByte) { \
|
|
return r | (static_cast<T>(b - end_byte_marker) << bit_start); \
|
|
} \
|
|
r |= b << bit_start;
|
|
|
|
// The end of the unrolled loop.
|
|
#define UNROLLED_END(bit_start) \
|
|
static_assert(bit_start % kDataBitsPerByte == 0, \
|
|
"Bit start must be a multiple of the data bits per byte"); \
|
|
static_assert(bit_start >= 0 && bit_start < kBitsPerByte * sizeof(T), \
|
|
"Starting unrolled end at invalid bit position"); \
|
|
static_assert(bit_start + kDataBitsPerByte >= kBitsPerByte * sizeof(T), \
|
|
"Unrolled end does not contain final bits in value"); \
|
|
b = ReadByte(); \
|
|
ASSERT(b > kMaxUnsignedDataPerByte); \
|
|
return r | (static_cast<T>(b - end_byte_marker) << bit_start);
|
|
|
|
uint16_t Read16(uint8_t end_byte_marker) {
|
|
using T = uint16_t;
|
|
UNROLLED_INIT();
|
|
UNROLLED_BODY(7);
|
|
UNROLLED_END(14);
|
|
}
|
|
|
|
uint32_t Read32(uint8_t end_byte_marker) {
|
|
using T = uint32_t;
|
|
UNROLLED_INIT();
|
|
UNROLLED_BODY(7);
|
|
UNROLLED_BODY(14);
|
|
UNROLLED_BODY(21);
|
|
UNROLLED_END(28);
|
|
}
|
|
|
|
uint64_t Read64(uint8_t end_byte_marker) {
|
|
using T = uint64_t;
|
|
UNROLLED_INIT();
|
|
UNROLLED_BODY(7);
|
|
UNROLLED_BODY(14);
|
|
UNROLLED_BODY(21);
|
|
UNROLLED_BODY(28);
|
|
UNROLLED_BODY(35);
|
|
UNROLLED_BODY(42);
|
|
UNROLLED_BODY(49);
|
|
UNROLLED_BODY(56);
|
|
UNROLLED_END(63);
|
|
}
|
|
|
|
DART_FORCE_INLINE uint8_t ReadByte() {
|
|
ASSERT(current_ < end_);
|
|
return *current_++;
|
|
}
|
|
|
|
private:
|
|
const uint8_t* buffer_;
|
|
const uint8_t* current_;
|
|
const uint8_t* end_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(ReadStream);
|
|
};
|
|
|
|
// Base class for streams that writing various types into a buffer, possibly
|
|
// flushing data out periodically to a more permanent store.
|
|
class BaseWriteStream : public ValueObject {
|
|
public:
|
|
explicit BaseWriteStream(intptr_t initial_size)
|
|
: initial_size_(Utils::RoundUpToPowerOfTwo(initial_size)) {}
|
|
virtual ~BaseWriteStream() {}
|
|
|
|
DART_FORCE_INLINE intptr_t bytes_written() const { return Position(); }
|
|
virtual intptr_t Position() const { return current_ - buffer_; }
|
|
|
|
intptr_t Align(intptr_t alignment) {
|
|
const intptr_t position_before = Position();
|
|
const intptr_t position_after = Utils::RoundUp(position_before, alignment);
|
|
const intptr_t length = position_after - position_before;
|
|
if (length != 0) {
|
|
EnsureSpace(length);
|
|
memset(current_, 0, length);
|
|
SetPosition(position_after);
|
|
}
|
|
return length;
|
|
}
|
|
|
|
template <int N, typename T>
|
|
class Raw {};
|
|
|
|
template <typename T>
|
|
class Raw<1, T> {
|
|
public:
|
|
static void Write(BaseWriteStream* st, T value) {
|
|
st->WriteByte(bit_cast<uint8_t>(value));
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
class Raw<2, T> {
|
|
public:
|
|
static void Write(BaseWriteStream* st, T value) {
|
|
st->Write<int16_t>(bit_cast<int16_t>(value));
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
class Raw<4, T> {
|
|
public:
|
|
static void Write(BaseWriteStream* st, T value) {
|
|
st->Write<int32_t>(bit_cast<int32_t>(value));
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
class Raw<8, T> {
|
|
public:
|
|
static void Write(BaseWriteStream* st, T value) {
|
|
st->Write<int64_t>(bit_cast<int64_t>(value));
|
|
}
|
|
};
|
|
|
|
void WriteWordWith32BitWrites(uword value) {
|
|
constexpr intptr_t kNumWrite32PerWord = kBitsPerWord / kBitsPerInt32;
|
|
|
|
const uint32_t mask = Utils::NBitMask(kBitsPerInt32);
|
|
for (intptr_t j = 0; j < kNumWrite32PerWord; j++) {
|
|
const uint32_t shifted_value = (value >> (j * kBitsPerInt32));
|
|
Raw<kInt32Size, uint32_t>::Write(this, shifted_value & mask);
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
void WriteUnsigned(T value) {
|
|
ASSERT(value >= 0);
|
|
while (value > kMaxUnsignedDataPerByte) {
|
|
WriteByte(static_cast<uint8_t>(value & kByteMask));
|
|
value = value >> kDataBitsPerByte;
|
|
}
|
|
WriteByte(static_cast<uint8_t>(value + kEndUnsignedByteMarker));
|
|
}
|
|
|
|
void WriteBytes(const void* addr, intptr_t len) {
|
|
if (len != 0) {
|
|
EnsureSpace(len);
|
|
memmove(current_, addr, len);
|
|
current_ += len;
|
|
}
|
|
}
|
|
|
|
void WriteWord(uword value) { WriteFixed(value); }
|
|
|
|
void WriteTargetWord(word value);
|
|
|
|
void Printf(const char* format, ...) PRINTF_ATTRIBUTE(2, 3) {
|
|
va_list args;
|
|
va_start(args, format);
|
|
VPrintf(format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void VPrintf(const char* format, va_list args) {
|
|
// Measure.
|
|
va_list measure_args;
|
|
va_copy(measure_args, args);
|
|
intptr_t len = Utils::VSNPrint(nullptr, 0, format, measure_args);
|
|
va_end(measure_args);
|
|
|
|
// Alloc.
|
|
EnsureSpace(len + 1);
|
|
|
|
// Print.
|
|
va_list print_args;
|
|
va_copy(print_args, args);
|
|
Utils::VSNPrint(reinterpret_cast<char*>(current_), len + 1, format,
|
|
print_args);
|
|
va_end(print_args);
|
|
current_ += len; // Not len + 1 to swallow the terminating NUL.
|
|
}
|
|
|
|
template <typename T>
|
|
void Write(T value) {
|
|
T v = value;
|
|
while (v < kMinDataPerByte || v > kMaxDataPerByte) {
|
|
WriteByte(static_cast<uint8_t>(v & kByteMask));
|
|
v = v >> kDataBitsPerByte;
|
|
}
|
|
WriteByte(static_cast<uint8_t>(v + kEndByteMarker));
|
|
}
|
|
|
|
template <typename T>
|
|
void WriteFixed(T value) {
|
|
WriteBytes(&value, sizeof(value));
|
|
}
|
|
|
|
DART_FORCE_INLINE void WriteByte(uint8_t value) {
|
|
EnsureSpace(1);
|
|
*current_++ = value;
|
|
}
|
|
|
|
void WriteString(const char* cstr) { WriteBytes(cstr, strlen(cstr)); }
|
|
|
|
private:
|
|
using C = LEB128Constants;
|
|
|
|
public:
|
|
template <typename T>
|
|
C::only_if_unsigned<T, void> WriteLEB128(T value) {
|
|
T remainder = value;
|
|
bool is_last_part;
|
|
do {
|
|
uint8_t part = static_cast<uint8_t>(remainder & C::kDataByteMask);
|
|
remainder >>= C::kDataBitsPerByte;
|
|
// For unsigned types, we're done when the remainder has no bits set.
|
|
is_last_part = remainder == static_cast<T>(0);
|
|
if (!is_last_part) {
|
|
// Mark this part as a non-final part for this value.
|
|
part |= C::kMoreDataMask;
|
|
}
|
|
WriteByte(part);
|
|
} while (!is_last_part);
|
|
}
|
|
|
|
template <typename T>
|
|
C::only_if_signed<T, void> WriteLEB128(T value) {
|
|
// If we're trying to LEB128 encode a negative value, chances are we should
|
|
// be using SLEB128 instead.
|
|
ASSERT(value >= 0);
|
|
return WriteLEB128(bit_cast<typename std::make_unsigned<T>::type>(value));
|
|
}
|
|
|
|
template <typename T>
|
|
C::only_if_signed<T, void> WriteSLEB128(T value) {
|
|
constexpr intptr_t kBitsPerT = kBitsPerByte * sizeof(T);
|
|
using Unsigned = typename std::make_unsigned<T>::type;
|
|
// Record whether the original value was negative.
|
|
const bool is_negative = value < 0;
|
|
T remainder = value;
|
|
bool is_last_part;
|
|
do {
|
|
uint8_t part = static_cast<uint8_t>(remainder & C::kDataByteMask);
|
|
remainder >>= C::kDataBitsPerByte;
|
|
// For signed types, we're done when either:
|
|
// - the remainder has all bits set and the part's sign bit is set
|
|
// for negative values, or
|
|
// - the remainder has no bits set and the part's sign bit is unset for
|
|
// non-negative values.
|
|
// If the remainder matches but the sign bit does not, we need one more
|
|
// part to set the sign bit correctly when decoding.
|
|
if (is_negative) {
|
|
// Right shifts of negative values in C are not guaranteed to be
|
|
// arithmetic. For negative values, set the [kDataBitsPerByte] most
|
|
// significant bits after shifting to ensure the value stays negative.
|
|
constexpr intptr_t preserved_bits = kBitsPerT - C::kDataBitsPerByte;
|
|
// The sign extension mask is the inverse of the preserved bits mask.
|
|
constexpr T sign_extend =
|
|
~static_cast<T>((static_cast<Unsigned>(1) << preserved_bits) - 1);
|
|
// Sign extend for negative values just in case a non-arithmetic right
|
|
// shift is used by the compiler.
|
|
remainder |= sign_extend;
|
|
ASSERT(remainder < 0); // Remainder should still be negative.
|
|
is_last_part =
|
|
remainder == ~static_cast<T>(0) && (part & C::kSignMask) != 0;
|
|
} else {
|
|
ASSERT(remainder >= 0); // Remainder should still be non-negative.
|
|
is_last_part =
|
|
(remainder == static_cast<T>(0) && (part & C::kSignMask) == 0);
|
|
}
|
|
if (!is_last_part) {
|
|
// Mark this part as a non-final part for this value.
|
|
part |= C::kMoreDataMask;
|
|
}
|
|
WriteByte(part);
|
|
} while (!is_last_part);
|
|
}
|
|
|
|
template <typename T>
|
|
C::only_if_unsigned<T, void> WriteSLEB128(T value) {
|
|
return WriteSLEB128(bit_cast<typename std::make_signed<T>::type>(value));
|
|
}
|
|
|
|
protected:
|
|
void EnsureSpace(intptr_t size_needed) {
|
|
if (Remaining() >= size_needed) return;
|
|
intptr_t increment_size = capacity_;
|
|
if (size_needed > increment_size) {
|
|
increment_size = Utils::RoundUp(size_needed, initial_size_);
|
|
}
|
|
intptr_t new_size = capacity_ + increment_size;
|
|
ASSERT(new_size > capacity_);
|
|
Realloc(new_size);
|
|
if (buffer_ == nullptr) {
|
|
Exceptions::ThrowOOM();
|
|
}
|
|
ASSERT(Remaining() >= size_needed);
|
|
}
|
|
|
|
virtual void SetPosition(intptr_t value) {
|
|
EnsureSpace(value - BaseWriteStream::Position());
|
|
current_ = buffer_ + value;
|
|
}
|
|
|
|
DART_FORCE_INLINE intptr_t Remaining() const {
|
|
return capacity_ - BaseWriteStream::Position();
|
|
}
|
|
|
|
// Resizes the internal buffer to the requested new capacity. Should set
|
|
// buffer_, capacity_, and current_ appropriately.
|
|
//
|
|
// Instead of templating over an Allocator (which would then cause users
|
|
// of the templated class to need to be templated, etc.), we just add an
|
|
// Realloc method to override appropriately in subclasses. Less flexible,
|
|
// but requires less changes throughout the codebase.
|
|
virtual void Realloc(intptr_t new_capacity) = 0;
|
|
|
|
const intptr_t initial_size_;
|
|
uint8_t* buffer_ = nullptr;
|
|
uint8_t* current_ = nullptr;
|
|
intptr_t capacity_ = 0;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(BaseWriteStream);
|
|
};
|
|
|
|
// A base class for non-streaming write streams. Since these streams are
|
|
// not flushed periodically, the internal buffer contains all written data
|
|
// and can be retrieved via buffer(). NonStreamingWriteStream also provides
|
|
// SetPosition as part of its public API for non-sequential writing.
|
|
class NonStreamingWriteStream : public BaseWriteStream {
|
|
public:
|
|
explicit NonStreamingWriteStream(intptr_t initial_size)
|
|
: BaseWriteStream(initial_size) {}
|
|
|
|
public:
|
|
uint8_t* buffer() const { return buffer_; }
|
|
|
|
// Sets the position of the buffer
|
|
DART_FORCE_INLINE void SetPosition(intptr_t value) {
|
|
BaseWriteStream::SetPosition(value);
|
|
}
|
|
};
|
|
|
|
// A non-streaming write stream that uses realloc for reallocation, and frees
|
|
// the buffer when destructed unless ownership is transfered using Steal().
|
|
class MallocWriteStream : public NonStreamingWriteStream {
|
|
public:
|
|
explicit MallocWriteStream(intptr_t initial_size)
|
|
: NonStreamingWriteStream(initial_size) {}
|
|
~MallocWriteStream();
|
|
|
|
// Resets the stream and returns the original buffer, which is now considered
|
|
// owned by the caller. Sets [*length] to the length of the returned buffer.
|
|
uint8_t* Steal(intptr_t* length) {
|
|
ASSERT(length != nullptr);
|
|
*length = bytes_written();
|
|
uint8_t* const old_buffer = buffer_;
|
|
// We don't immediately reallocate a new space just in case this steal
|
|
// is the last use of this stream.
|
|
current_ = buffer_ = nullptr;
|
|
capacity_ = 0;
|
|
return old_buffer;
|
|
}
|
|
|
|
private:
|
|
virtual void Realloc(intptr_t new_size);
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(MallocWriteStream);
|
|
};
|
|
|
|
// A non-streaming write stream that uses a zone for reallocation.
|
|
class ZoneWriteStream : public NonStreamingWriteStream {
|
|
public:
|
|
ZoneWriteStream(Zone* zone, intptr_t initial_size)
|
|
: NonStreamingWriteStream(initial_size), zone_(zone) {}
|
|
|
|
private:
|
|
virtual void Realloc(intptr_t new_size);
|
|
|
|
Zone* const zone_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(ZoneWriteStream);
|
|
};
|
|
|
|
// A streaming write stream that uses the internal buffer only for non-flushed
|
|
// data. Like MallocWriteStream, uses realloc for reallocation, and flushes and
|
|
// frees the internal buffer when destructed. Since part or all of the written
|
|
// data may be flushed and no longer in the internal buffer, it does not provide
|
|
// a way to retrieve the written contents.
|
|
class StreamingWriteStream : public BaseWriteStream {
|
|
public:
|
|
explicit StreamingWriteStream(intptr_t initial_capacity,
|
|
Dart_StreamingWriteCallback callback,
|
|
void* callback_data)
|
|
: BaseWriteStream(initial_capacity),
|
|
callback_(callback),
|
|
callback_data_(callback_data) {}
|
|
~StreamingWriteStream();
|
|
|
|
private:
|
|
// Flushes any unflushed data to callback_data and resets the internal
|
|
// buffer. Changes current_ and flushed_size_ accordingly.
|
|
virtual void Flush();
|
|
|
|
virtual void Realloc(intptr_t new_size);
|
|
|
|
virtual intptr_t Position() const {
|
|
return flushed_size_ + BaseWriteStream::Position();
|
|
}
|
|
|
|
virtual void SetPosition(intptr_t value) {
|
|
// Make sure we're not trying to set the position to already-flushed data.
|
|
ASSERT(value >= flushed_size_);
|
|
BaseWriteStream::SetPosition(value - flushed_size_);
|
|
}
|
|
|
|
const Dart_StreamingWriteCallback callback_;
|
|
void* const callback_data_;
|
|
intptr_t flushed_size_ = 0;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(StreamingWriteStream);
|
|
};
|
|
|
|
} // namespace dart
|
|
|
|
#endif // RUNTIME_VM_DATASTREAM_H_
|