84a1b114cd
This CL pulls out the refactorings used to support the new MachOWriter in a followup CL to allow them to be reviewed separately. Rename Elf -> ElfWriter. Also rename model classes used by ElfWriter for concepts that exist both in ELF and Mach-O to ElfX. For example, the old ELF-specific SymbolTable is renamed to ElfSymbolTable. Adds SharedObjectWriter to serve as a base class for both ElfWriter and the upcoming MachOWriter. Adds a new AbstractWriteStream that serves as a common superclass of both BaseWriteStream and SharedObjectWriter::WriteStream and allows the creation of fully delegating WriteStreams that do not maintain a local buffer. Abstract the old Elf::SymbolData class into SharedObjectWriter::SymbolData, which stores an enum value as the type of the symbol instead of storing the ELF encoding of the type. Rename the DwarfElfStream (which actually wasn't ELF specific, as all the ELF-specific DWARF information is handled by ElfWriter) to DwarfSharedObjectStream and put it in a separate header file. Rename Image::compiled_to_elf() to Image::compiled_to_shared_object() and add a separate Image::compiled_to_elf() that checks for the ELF magic value at the DSO base. Also add Image::shared_object_start() and Image::build_id_start() to return pointers to the DSO base and the build ID note, respectively. Refactor Image::build_id() and Image::build_id_length() to check compiled_for_elf() prior to decoding the data pointed to by build_id_start() as an ELF note section. Create an AOTSnapshotType enum to specific the snapshot writer to use in CreateAppAOTSnapshot instead of using an as_elf boolean. TEST=refactorings, so existing tests on ci Change-Id: Ia3ab37a4dff93b6e00390b123753be5a51fbdaaa Cq-Include-Trybots: luci.dart.try:vm-aot-linux-debug-x64-try,vm-aot-mac-release-arm64-try,vm-aot-dwarf-linux-product-x64-try,vm-linux-debug-x64-try,vm-mac-debug-arm64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/421301 Reviewed-by: Ryan Macnak <rmacnak@google.com> Commit-Queue: Tess Strickland <sstrickl@google.com>
719 lines
23 KiB
C++
719 lines
23 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 constexpr int8_t kDataBitsPerByte = 7;
|
|
static constexpr int8_t kByteMask = (1 << kDataBitsPerByte) - 1;
|
|
static constexpr int8_t kMaxUnsignedDataPerByte = kByteMask;
|
|
static constexpr int8_t kMinDataPerByte = -(1 << (kDataBitsPerByte - 1));
|
|
static constexpr int8_t kMaxDataPerByte =
|
|
(~kMinDataPerByte & kByteMask); // NOLINT
|
|
static constexpr uint8_t kEndByteMarker = (255 - kMaxDataPerByte);
|
|
static constexpr 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(void* 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 ReadRefId() {
|
|
const int8_t* cursor = reinterpret_cast<const int8_t*>(current_);
|
|
intptr_t result = 0;
|
|
intptr_t byte;
|
|
// clang-format off
|
|
#define STAGE \
|
|
byte = *cursor++; /* ldrsb byte, [result], 1 */ \
|
|
result = byte + (result << 7); /* add result, byte, result lsl 7 */ \
|
|
if (byte < 0) goto done; /* tbnz byte, 63, done */
|
|
STAGE // 0-7
|
|
STAGE // 8-14
|
|
STAGE // 15-21
|
|
STAGE // 22-28
|
|
#undef STAGE
|
|
ASSERT(byte < 0); // 256MB is enough for anyone...
|
|
// clang-format on
|
|
done:
|
|
current_ = reinterpret_cast<const uint8_t*>(cursor);
|
|
// With big-endian order and the has-more marker being 0, the correction
|
|
// factor to remove the last-byte marker is a constant, which can be folded
|
|
// into subsequent load offsets.
|
|
return result + 128;
|
|
}
|
|
|
|
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 offset = 0) {
|
|
intptr_t position_before = Position();
|
|
intptr_t position_after =
|
|
Utils::RoundUp(position_before, alignment, offset);
|
|
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:
|
|
ReadStream(const uint8_t* buffer, const uint8_t* current, const uint8_t* end)
|
|
: buffer_(buffer), current_(current), end_(end) {}
|
|
|
|
const uint8_t* buffer_;
|
|
const uint8_t* current_;
|
|
const uint8_t* end_;
|
|
|
|
friend class Deserializer;
|
|
DISALLOW_COPY_AND_ASSIGN(ReadStream);
|
|
};
|
|
|
|
// Base class for streams that write bytes to some backing store. Generally,
|
|
// BaseWriteStream is a more appropriate superclass of new WriteStreams since
|
|
// it offers more functionality (e.g., Printf).
|
|
struct AbstractWriteStream : public ValueObject {
|
|
AbstractWriteStream() {}
|
|
virtual ~AbstractWriteStream() {}
|
|
|
|
virtual intptr_t Position() const = 0;
|
|
virtual intptr_t Align(intptr_t alignment, intptr_t offset = 0) = 0;
|
|
virtual void WriteBytes(const void* addr, intptr_t len) = 0;
|
|
virtual void WriteByte(uint8_t value) = 0;
|
|
|
|
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));
|
|
}
|
|
};
|
|
|
|
// Base class for streams that writing various types into a buffer, possibly
|
|
// flushing data out periodically to a more permanent store.
|
|
class BaseWriteStream : public AbstractWriteStream {
|
|
public:
|
|
explicit BaseWriteStream(intptr_t initial_size)
|
|
: initial_size_(Utils::RoundUpToPowerOfTwo(initial_size)) {}
|
|
|
|
DART_FORCE_INLINE intptr_t bytes_written() const { return Position(); }
|
|
virtual intptr_t Position() const { return current_ - buffer_; }
|
|
intptr_t initial_size() const { return initial_size_; }
|
|
|
|
virtual intptr_t Align(intptr_t alignment, intptr_t offset = 0) {
|
|
const intptr_t position_before = Position();
|
|
const intptr_t position_after =
|
|
Utils::RoundUp(position_before, alignment, offset);
|
|
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 WriteRefId(intptr_t value) {
|
|
ASSERT(Utils::IsUint(28, value)); // 256MB is enough for anyone...
|
|
EnsureSpace(4);
|
|
if ((value >> 21) != 0) {
|
|
*current_++ = (value >> 21) & 127;
|
|
}
|
|
if ((value >> 14) != 0) {
|
|
*current_++ = (value >> 14) & 127;
|
|
}
|
|
if ((value >> 7) != 0) {
|
|
*current_++ = (value >> 7) & 127;
|
|
}
|
|
*current_++ = ((value >> 0) & 127) | 128;
|
|
}
|
|
|
|
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 virtual void WriteByte(uint8_t value) {
|
|
EnsureSpace(1);
|
|
*current_++ = value;
|
|
}
|
|
|
|
void WriteString(const char* cstr) { WriteBytes(cstr, strlen(cstr)); }
|
|
|
|
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 transferred 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_
|