blob: 357075f3cf04aa117517e761b9056fb27c20fba0 [file] [log] [blame]
//===--- 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.writeRaw
/// 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 the same form in which it is
/// represented on the serializing machine.
template <typename T>
llvm::Error writeRaw(T Value) {
// FIXME: We implicitly inherit the endianess of the serializing machine.
// Since we're currently only supporting macOS that's not a problem for now.
auto Error = Stream.writeRaw(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 = writeRaw(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.writeRaw(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 = writeRaw(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 = writeRaw(ValueSize);
(void)SizeError;
assert(!SizeError);
auto ContentError = writeRaw(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