mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
This change fixes the ExponentialGrowthAppendingBinaryByteStream tests on big endian machines. Force ExponentialGrowthAppendingBinaryByteStreams to use little- endian byte order. We always used little-endian byte order anyway and it seems very unlikely we'll need the flexibility to make the stream big-endian in the future. The benefit of this is that we can use portable APIs while still allowing the compiler to remove conditional byte swaps. Also replace writeRaw with writeInteger and make it explicitly little-endian to make the API cleaner and more portable.
364 lines
12 KiB
C++
364 lines
12 KiB
C++
//===--- ByteTreeSerialization.h - ByteTree serialization -------*- C++ -*-===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
/// \file
|
|
/// Provides an interface for serializing an object tree to a custom
|
|
/// binary format called ByteTree.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef SWIFT_BASIC_BYTETREESERIALIZATION_H
|
|
#define SWIFT_BASIC_BYTETREESERIALIZATION_H
|
|
|
|
#include "llvm/Support/BinaryStreamError.h"
|
|
#include "llvm/Support/BinaryStreamWriter.h"
|
|
#include "swift/Basic/ExponentialGrowthAppendingBinaryByteStream.h"
|
|
#include <map>
|
|
|
|
namespace {
|
|
// Only used by compiler if both template types are the same
|
|
template <typename T, T>
|
|
struct SameType;
|
|
} // anonymous namespace
|
|
|
|
namespace swift {
|
|
namespace byteTree {
|
|
class ByteTreeWriter;
|
|
|
|
using UserInfoMap = std::map<void *, void *>;
|
|
|
|
/// Add a template specialization of \c ObjectTraits for any type that
|
|
/// serializes as an object consisting of multiple fields.
|
|
template <class T>
|
|
struct ObjectTraits {
|
|
// Must provide:
|
|
|
|
/// Return the number of fields that will be written in \c write when
|
|
/// \p Object gets serialized. \p UserInfo can contain arbitrary values that
|
|
/// can modify the serialization behaviour and gets passed down from the
|
|
/// serialization invocation.
|
|
// static unsigned numFields(const T &Object, UserInfoMap &UserInfo);
|
|
|
|
/// Serialize \p Object by calling \c Writer.write for all the fields of
|
|
/// \p Object. \p UserInfo can contain arbitrary values that can modify the
|
|
/// serialization behaviour and gets passed down from the serialization
|
|
/// invocation.
|
|
// static void write(ByteTreeWriter &Writer, const T &Object,
|
|
// UserInfoMap &UserInfo);
|
|
};
|
|
|
|
/// Add a template specialization of \c ScalarTraits for any type that
|
|
/// serializes into a raw set of bytes.
|
|
template <class T>
|
|
struct ScalarTraits {
|
|
// Must provide:
|
|
|
|
/// Return the number of bytes the serialized format of \p Value will take up.
|
|
// static unsigned size(const T &Value);
|
|
|
|
/// Serialize \p Value by writing its binary format into \p Writer. Any errors
|
|
/// that may be returned by \p Writer can be returned by this function and
|
|
/// will be handled on the call-side.
|
|
// static llvm::Error write(llvm::BinaryStreamWriter &Writer, const T &Value);
|
|
};
|
|
|
|
/// Add a template specialization of \c DirectlyEncodable for any type whose
|
|
/// serialized form is equal to its binary representation on the serializing
|
|
/// machine.
|
|
template <class T>
|
|
struct DirectlyEncodable {
|
|
// Must provide:
|
|
|
|
// static bool const value = true;
|
|
};
|
|
|
|
/// Add a template specialization of \c WrapperTypeTraits for any type that
|
|
/// serializes as a type that already has a specialization of \c ScalarTypes.
|
|
/// This will typically be useful for types like enums that have a 1-to-1
|
|
/// mapping to e.g. an integer.
|
|
template <class T>
|
|
struct WrapperTypeTraits {
|
|
// Must provide:
|
|
|
|
/// Write the serializable representation of \p Value to \p Writer. This will
|
|
/// typically take the form \c Writer.write(convertedValue(Value), Index)
|
|
/// where \c convertedValue has to be defined.
|
|
// static void write(ByteTreeWriter &Writer, const T &Value, unsigned Index);
|
|
};
|
|
|
|
// Test if ObjectTraits<T> is defined on type T.
|
|
template <class T>
|
|
struct has_ObjectTraits {
|
|
using Signature_numFields = unsigned (*)(const T &, UserInfoMap &UserInfo);
|
|
using Signature_write = void (*)(ByteTreeWriter &Writer, const T &Object,
|
|
UserInfoMap &UserInfo);
|
|
|
|
template <typename U>
|
|
static char test(SameType<Signature_numFields, &U::numFields> *,
|
|
SameType<Signature_write, &U::write> *);
|
|
|
|
template <typename U>
|
|
static double test(...);
|
|
|
|
public:
|
|
static bool const value =
|
|
(sizeof(test<ObjectTraits<T>>(nullptr, nullptr)) == 1);
|
|
};
|
|
|
|
// Test if ScalarTraits<T> is defined on type T.
|
|
template <class T>
|
|
struct has_ScalarTraits {
|
|
using Signature_size = unsigned (*)(const T &Object);
|
|
using Signature_write = llvm::Error (*)(llvm::BinaryStreamWriter &Writer,
|
|
const T &Object);
|
|
|
|
template <typename U>
|
|
static char test(SameType<Signature_size, &U::size> *,
|
|
SameType<Signature_write, &U::write> *);
|
|
|
|
template <typename U>
|
|
static double test(...);
|
|
|
|
public:
|
|
static bool const value =
|
|
(sizeof(test<ScalarTraits<T>>(nullptr, nullptr)) == 1);
|
|
};
|
|
|
|
// Test if WrapperTypeTraits<T> is defined on type T.
|
|
template <class T>
|
|
struct has_WrapperTypeTraits {
|
|
using Signature_write = void (*)(ByteTreeWriter &Writer, const T &Object,
|
|
unsigned Index);
|
|
|
|
template <typename U>
|
|
static char test(SameType<Signature_write, &U::write> *);
|
|
|
|
template <typename U>
|
|
static double test(...);
|
|
|
|
public:
|
|
static bool const value = (sizeof(test<WrapperTypeTraits<T>>(nullptr)) == 1);
|
|
};
|
|
|
|
class ByteTreeWriter {
|
|
private:
|
|
/// The writer to which the binary data is written.
|
|
llvm::BinaryStreamWriter &StreamWriter;
|
|
|
|
/// The underlying stream of the StreamWriter. We need this reference so that
|
|
/// we can call \c ExponentialGrowthAppendingBinaryByteStream.writeInteger
|
|
/// which is more efficient than the generic \c writeBytes of
|
|
/// \c llvm::BinaryStreamWriter since it avoids the arbitrary size memcopy.
|
|
ExponentialGrowthAppendingBinaryByteStream &Stream;
|
|
|
|
/// The number of fields this object contains. \c UINT_MAX if it has not been
|
|
/// set yet. No member may be written to the object if expected number of
|
|
/// fields has not been set yet.
|
|
unsigned NumFields = UINT_MAX;
|
|
|
|
/// The index of the next field to write. Used in assertion builds to keep
|
|
/// track that no indicies are jumped and that the object contains the
|
|
/// expected number of fields.
|
|
unsigned CurrentFieldIndex = 0;
|
|
|
|
UserInfoMap &UserInfo;
|
|
|
|
/// The \c ByteTreeWriter can only be constructed internally. Use
|
|
/// \c ByteTreeWriter.write to serialize a new object.
|
|
/// \p Stream must be the underlying stream of \p SteamWriter.
|
|
ByteTreeWriter(ExponentialGrowthAppendingBinaryByteStream &Stream,
|
|
llvm::BinaryStreamWriter &StreamWriter, UserInfoMap &UserInfo)
|
|
: StreamWriter(StreamWriter), Stream(Stream), UserInfo(UserInfo) {}
|
|
|
|
/// Write the given value to the ByteTree in little-endian byte order.
|
|
template <typename T>
|
|
llvm::Error writeInteger(T Value) {
|
|
auto Error = Stream.writeInteger(StreamWriter.getOffset(), Value);
|
|
StreamWriter.setOffset(StreamWriter.getOffset() + sizeof(T));
|
|
return Error;
|
|
}
|
|
|
|
/// Set the expected number of fields the object written by this writer is
|
|
/// expected to have.
|
|
void setNumFields(uint32_t NumFields) {
|
|
assert(NumFields != UINT_MAX &&
|
|
"NumFields may not be reset since it has already been written to "
|
|
"the byte stream");
|
|
assert((this->NumFields == UINT_MAX) && "NumFields has already been set");
|
|
// Num fields cannot exceed (1 << 31) since it would otherwise interfere
|
|
// with the bitflag that indicates if the next construct in the tree is an
|
|
// object or a scalar.
|
|
assert((NumFields & ((uint32_t)1 << 31)) == 0 && "Field size too large");
|
|
|
|
// Set the most significant bit to indicate that the next construct is an
|
|
// object and not a scalar.
|
|
uint32_t ToWrite = NumFields | (1 << 31);
|
|
auto Error = writeInteger(ToWrite);
|
|
(void)Error;
|
|
assert(!Error);
|
|
|
|
this->NumFields = NumFields;
|
|
}
|
|
|
|
/// Validate that \p Index is the next field that is expected to be written,
|
|
/// does not exceed the number of fields in this object and that
|
|
/// \c setNumFields has already been called.
|
|
void validateAndIncreaseFieldIndex(unsigned Index) {
|
|
assert((NumFields != UINT_MAX) &&
|
|
"setNumFields must be called before writing any value");
|
|
assert(Index == CurrentFieldIndex && "Writing index out of order");
|
|
assert(Index < NumFields &&
|
|
"Writing more fields than object is expected to have");
|
|
|
|
CurrentFieldIndex++;
|
|
}
|
|
|
|
~ByteTreeWriter() {
|
|
assert(CurrentFieldIndex == NumFields &&
|
|
"Object had more or less elements than specified");
|
|
}
|
|
|
|
public:
|
|
/// Write a binary serialization of \p Object to \p StreamWriter, prefixing
|
|
/// the stream by the specified ProtocolVersion.
|
|
template <typename T>
|
|
typename std::enable_if<has_ObjectTraits<T>::value, void>::type
|
|
static write(ExponentialGrowthAppendingBinaryByteStream &Stream,
|
|
uint32_t ProtocolVersion, const T &Object,
|
|
UserInfoMap &UserInfo) {
|
|
llvm::BinaryStreamWriter StreamWriter(Stream);
|
|
ByteTreeWriter Writer(Stream, StreamWriter, UserInfo);
|
|
|
|
auto Error = Writer.writeInteger(ProtocolVersion);
|
|
(void)Error;
|
|
assert(!Error);
|
|
|
|
// There always is one root. We need to set NumFields so that index
|
|
// validation succeeds, but we don't want to serialize this.
|
|
Writer.NumFields = 1;
|
|
Writer.write(Object, /*Index=*/0);
|
|
}
|
|
|
|
template <typename T>
|
|
typename std::enable_if<has_ObjectTraits<T>::value, void>::type
|
|
write(const T &Object, unsigned Index) {
|
|
validateAndIncreaseFieldIndex(Index);
|
|
|
|
auto ObjectWriter = ByteTreeWriter(Stream, StreamWriter, UserInfo);
|
|
ObjectWriter.setNumFields(ObjectTraits<T>::numFields(Object, UserInfo));
|
|
|
|
ObjectTraits<T>::write(ObjectWriter, Object, UserInfo);
|
|
}
|
|
|
|
template <typename T>
|
|
typename std::enable_if<has_ScalarTraits<T>::value, void>::type
|
|
write(const T &Value, unsigned Index) {
|
|
validateAndIncreaseFieldIndex(Index);
|
|
|
|
uint32_t ValueSize = ScalarTraits<T>::size(Value);
|
|
// Size cannot exceed (1 << 31) since it would otherwise interfere with the
|
|
// bitflag that indicates if the next construct in the tree is an object
|
|
// or a scalar.
|
|
assert((ValueSize & ((uint32_t)1 << 31)) == 0 && "Value size too large");
|
|
auto SizeError = writeInteger(ValueSize);
|
|
(void)SizeError;
|
|
assert(!SizeError);
|
|
|
|
auto StartOffset = StreamWriter.getOffset();
|
|
auto ContentError = ScalarTraits<T>::write(StreamWriter, Value);
|
|
(void)ContentError;
|
|
assert(!ContentError);
|
|
(void)StartOffset;
|
|
assert((StreamWriter.getOffset() - StartOffset == ValueSize) &&
|
|
"Number of written bytes does not match size returned by "
|
|
"ScalarTraits<T>::size");
|
|
}
|
|
|
|
template <typename T>
|
|
typename std::enable_if<DirectlyEncodable<T>::value, void>::type
|
|
write(const T &Value, unsigned Index) {
|
|
validateAndIncreaseFieldIndex(Index);
|
|
|
|
uint32_t ValueSize = sizeof(T);
|
|
auto SizeError = writeInteger(ValueSize);
|
|
(void)SizeError;
|
|
assert(!SizeError);
|
|
|
|
auto ContentError = writeInteger(Value);
|
|
(void)ContentError;
|
|
assert(!ContentError);
|
|
}
|
|
|
|
template <typename T>
|
|
typename std::enable_if<has_WrapperTypeTraits<T>::value, void>::type
|
|
write(const T &Value, unsigned Index) {
|
|
auto LengthBeforeWrite = CurrentFieldIndex;
|
|
WrapperTypeTraits<T>::write(*this, Value, Index);
|
|
(void)LengthBeforeWrite;
|
|
assert(CurrentFieldIndex == LengthBeforeWrite + 1 &&
|
|
"WrapperTypeTraits did not call BinaryWriter.write");
|
|
}
|
|
};
|
|
|
|
// Define serialization schemes for common types
|
|
|
|
template <>
|
|
struct DirectlyEncodable<uint8_t> {
|
|
static bool const value = true;
|
|
};
|
|
|
|
template <>
|
|
struct DirectlyEncodable<uint16_t> {
|
|
static bool const value = true;
|
|
};
|
|
|
|
template <>
|
|
struct DirectlyEncodable<uint32_t> {
|
|
static bool const value = true;
|
|
};
|
|
|
|
template <>
|
|
struct WrapperTypeTraits<bool> {
|
|
static void write(ByteTreeWriter &Writer, const bool &Value,
|
|
unsigned Index) {
|
|
Writer.write(static_cast<uint8_t>(Value), Index);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct ScalarTraits<llvm::StringRef> {
|
|
static unsigned size(const llvm::StringRef &Str) { return Str.size(); }
|
|
static llvm::Error write(llvm::BinaryStreamWriter &Writer,
|
|
const llvm::StringRef &Str) {
|
|
return Writer.writeFixedString(Str);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct ObjectTraits<llvm::NoneType> {
|
|
// Serialize llvm::None as an object without any elements
|
|
static unsigned numFields(const llvm::NoneType &Object,
|
|
UserInfoMap &UserInfo) {
|
|
return 0;
|
|
}
|
|
|
|
static void write(ByteTreeWriter &Writer, const llvm::NoneType &Object,
|
|
UserInfoMap &UserInfo) {
|
|
// Nothing to write
|
|
}
|
|
};
|
|
|
|
} // end namespace byteTree
|
|
} // end namespace swift
|
|
|
|
#endif
|