Files
llvm-project/compiler-rt/lib/orc/simple_packed_serialization.h
Lang Hames bb41fc682e [ORC-RT][ORC][MachO] Add executor-side symbol tables to MachO platform support.
Adds symbol tables to the JITDylibState struct in the ORC runtime
MachOPlatformRuntimeState class. This table will hold the addresses of
materialized symbols (registered by a new JITLink pass in MachOPlatform),
allowing these to be looked up in the executor without an IPC request to the
controller.

The old lookup-symbols callback (made by the runtime in response to dlsym
lookups) is replaced with a push-symbols callback that can trigger
materialization of requested symbols.

Holding a symbol table on the executor side should make repeat calls to dlsym
(and other symbol lookup operations) cheaper since the IPC to trigger
materialization happens at most once per symbol. It should also enable us (at
some point in the future) to symbolicate backtraces in JIT'd code even if the
controller process is gone (e.g. detached or crashed). The trade-off for this
is increased memory consumption in the executor and larger JIT'd data transfers
(since symbol names are now transferred to the executor unconditionally, even
though they may never be used).
2023-12-02 15:25:25 -08:00

690 lines
22 KiB
C++

//===--- simple_packed_serialization.h - simple serialization ---*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file is a part of the ORC runtime support library.
//
// The behavior of the utilities in this header must be synchronized with the
// behavior of the utilities in
// llvm/ExecutionEngine/Orc/Shared/WrapperFunctionUtils.h.
//
// The Simple Packed Serialization (SPS) utilities are used to generate
// argument and return buffers for wrapper functions using the following
// serialization scheme:
//
// Primitives:
// bool, char, int8_t, uint8_t -- Two's complement 8-bit (0=false, 1=true)
// int16_t, uint16_t -- Two's complement 16-bit little endian
// int32_t, uint32_t -- Two's complement 32-bit little endian
// int64_t, int64_t -- Two's complement 64-bit little endian
//
// Sequence<T>:
// Serialized as the sequence length (as a uint64_t) followed by the
// serialization of each of the elements without padding.
//
// Tuple<T1, ..., TN>:
// Serialized as each of the element types from T1 to TN without padding.
//
//===----------------------------------------------------------------------===//
#ifndef ORC_RT_SIMPLE_PACKED_SERIALIZATION_H
#define ORC_RT_SIMPLE_PACKED_SERIALIZATION_H
#include "adt.h"
#include "endianness.h"
#include "error.h"
#include "stl_extras.h"
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>
namespace __orc_rt {
/// Output char buffer with overflow check.
class SPSOutputBuffer {
public:
SPSOutputBuffer(char *Buffer, size_t Remaining)
: Buffer(Buffer), Remaining(Remaining) {}
bool write(const char *Data, size_t Size) {
if (Size > Remaining)
return false;
memcpy(Buffer, Data, Size);
Buffer += Size;
Remaining -= Size;
return true;
}
private:
char *Buffer = nullptr;
size_t Remaining = 0;
};
/// Input char buffer with underflow check.
class SPSInputBuffer {
public:
SPSInputBuffer() = default;
SPSInputBuffer(const char *Buffer, size_t Remaining)
: Buffer(Buffer), Remaining(Remaining) {}
bool read(char *Data, size_t Size) {
if (Size > Remaining)
return false;
memcpy(Data, Buffer, Size);
Buffer += Size;
Remaining -= Size;
return true;
}
const char *data() const { return Buffer; }
bool skip(size_t Size) {
if (Size > Remaining)
return false;
Buffer += Size;
Remaining -= Size;
return true;
}
private:
const char *Buffer = nullptr;
size_t Remaining = 0;
};
/// Specialize to describe how to serialize/deserialize to/from the given
/// concrete type.
template <typename SPSTagT, typename ConcreteT, typename _ = void>
class SPSSerializationTraits;
/// A utility class for serializing to a blob from a variadic list.
template <typename... ArgTs> class SPSArgList;
// Empty list specialization for SPSArgList.
template <> class SPSArgList<> {
public:
static size_t size() { return 0; }
static bool serialize(SPSOutputBuffer &OB) { return true; }
static bool deserialize(SPSInputBuffer &IB) { return true; }
};
// Non-empty list specialization for SPSArgList.
template <typename SPSTagT, typename... SPSTagTs>
class SPSArgList<SPSTagT, SPSTagTs...> {
public:
template <typename ArgT, typename... ArgTs>
static size_t size(const ArgT &Arg, const ArgTs &...Args) {
return SPSSerializationTraits<SPSTagT, ArgT>::size(Arg) +
SPSArgList<SPSTagTs...>::size(Args...);
}
template <typename ArgT, typename... ArgTs>
static bool serialize(SPSOutputBuffer &OB, const ArgT &Arg,
const ArgTs &...Args) {
return SPSSerializationTraits<SPSTagT, ArgT>::serialize(OB, Arg) &&
SPSArgList<SPSTagTs...>::serialize(OB, Args...);
}
template <typename ArgT, typename... ArgTs>
static bool deserialize(SPSInputBuffer &IB, ArgT &Arg, ArgTs &...Args) {
return SPSSerializationTraits<SPSTagT, ArgT>::deserialize(IB, Arg) &&
SPSArgList<SPSTagTs...>::deserialize(IB, Args...);
}
};
/// SPS serialization for integral types, bool, and char.
template <typename SPSTagT>
class SPSSerializationTraits<
SPSTagT, SPSTagT,
std::enable_if_t<std::is_same<SPSTagT, bool>::value ||
std::is_same<SPSTagT, char>::value ||
std::is_same<SPSTagT, int8_t>::value ||
std::is_same<SPSTagT, int16_t>::value ||
std::is_same<SPSTagT, int32_t>::value ||
std::is_same<SPSTagT, int64_t>::value ||
std::is_same<SPSTagT, uint8_t>::value ||
std::is_same<SPSTagT, uint16_t>::value ||
std::is_same<SPSTagT, uint32_t>::value ||
std::is_same<SPSTagT, uint64_t>::value>> {
public:
static size_t size(const SPSTagT &Value) { return sizeof(SPSTagT); }
static bool serialize(SPSOutputBuffer &OB, const SPSTagT &Value) {
SPSTagT Tmp = Value;
if (IsBigEndianHost)
swapByteOrder(Tmp);
return OB.write(reinterpret_cast<const char *>(&Tmp), sizeof(Tmp));
}
static bool deserialize(SPSInputBuffer &IB, SPSTagT &Value) {
SPSTagT Tmp;
if (!IB.read(reinterpret_cast<char *>(&Tmp), sizeof(Tmp)))
return false;
if (IsBigEndianHost)
swapByteOrder(Tmp);
Value = Tmp;
return true;
}
};
/// Any empty placeholder suitable as a substitute for void when deserializing
class SPSEmpty {};
/// Represents an address in the executor.
class SPSExecutorAddr {};
/// SPS tag type for tuples.
///
/// A blob tuple should be serialized by serializing each of the elements in
/// sequence.
template <typename... SPSTagTs> class SPSTuple {
public:
/// Convenience typedef of the corresponding arg list.
typedef SPSArgList<SPSTagTs...> AsArgList;
};
/// SPS tag type for optionals.
///
/// SPSOptionals should be serialized as a bool with true indicating that an
/// SPSTagT value is present, and false indicating that there is no value.
/// If the boolean is true then the serialized SPSTagT will follow immediately
/// after it.
template <typename SPSTagT> class SPSOptional {};
/// SPS tag type for sequences.
///
/// SPSSequences should be serialized as a uint64_t sequence length,
/// followed by the serialization of each of the elements.
template <typename SPSElementTagT> class SPSSequence;
/// SPS tag type for strings, which are equivalent to sequences of chars.
using SPSString = SPSSequence<char>;
/// SPS tag type for maps.
///
/// SPS maps are just sequences of (Key, Value) tuples.
template <typename SPSTagT1, typename SPSTagT2>
using SPSMap = SPSSequence<SPSTuple<SPSTagT1, SPSTagT2>>;
/// Serialization for SPSEmpty type.
template <> class SPSSerializationTraits<SPSEmpty, SPSEmpty> {
public:
static size_t size(const SPSEmpty &EP) { return 0; }
static bool serialize(SPSOutputBuffer &OB, const SPSEmpty &BE) {
return true;
}
static bool deserialize(SPSInputBuffer &IB, SPSEmpty &BE) { return true; }
};
/// Specialize this to implement 'trivial' sequence serialization for
/// a concrete sequence type.
///
/// Trivial sequence serialization uses the sequence's 'size' member to get the
/// length of the sequence, and uses a range-based for loop to iterate over the
/// elements.
///
/// Specializing this template class means that you do not need to provide a
/// specialization of SPSSerializationTraits for your type.
template <typename SPSElementTagT, typename ConcreteSequenceT>
class TrivialSPSSequenceSerialization {
public:
static constexpr bool available = false;
};
/// Specialize this to implement 'trivial' sequence deserialization for
/// a concrete sequence type.
///
/// Trivial deserialization calls a static 'reserve(SequenceT&)' method on your
/// specialization (you must implement this) to reserve space, and then calls
/// a static 'append(SequenceT&, ElementT&) method to append each of the
/// deserialized elements.
///
/// Specializing this template class means that you do not need to provide a
/// specialization of SPSSerializationTraits for your type.
template <typename SPSElementTagT, typename ConcreteSequenceT>
class TrivialSPSSequenceDeserialization {
public:
static constexpr bool available = false;
};
/// Trivial std::string -> SPSSequence<char> serialization.
template <> class TrivialSPSSequenceSerialization<char, std::string> {
public:
static constexpr bool available = true;
};
/// Trivial SPSSequence<char> -> std::string deserialization.
template <> class TrivialSPSSequenceDeserialization<char, std::string> {
public:
static constexpr bool available = true;
using element_type = char;
static void reserve(std::string &S, uint64_t Size) { S.reserve(Size); }
static bool append(std::string &S, char C) {
S.push_back(C);
return true;
}
};
/// Trivial std::vector<T> -> SPSSequence<SPSElementTagT> serialization.
template <typename SPSElementTagT, typename T>
class TrivialSPSSequenceSerialization<SPSElementTagT, std::vector<T>> {
public:
static constexpr bool available = true;
};
/// Trivial span<T> -> SPSSequence<SPSElementTagT> serialization.
template <typename SPSElementTagT, typename T>
class TrivialSPSSequenceSerialization<SPSElementTagT, span<T>> {
public:
static constexpr bool available = true;
};
/// Trivial SPSSequence<SPSElementTagT> -> std::vector<T> deserialization.
template <typename SPSElementTagT, typename T>
class TrivialSPSSequenceDeserialization<SPSElementTagT, std::vector<T>> {
public:
static constexpr bool available = true;
using element_type = typename std::vector<T>::value_type;
static void reserve(std::vector<T> &V, uint64_t Size) { V.reserve(Size); }
static bool append(std::vector<T> &V, T E) {
V.push_back(std::move(E));
return true;
}
};
/// Trivial std::unordered_map<K, V> -> SPSSequence<SPSTuple<SPSKey, SPSValue>>
/// serialization.
template <typename SPSKeyTagT, typename SPSValueTagT, typename K, typename V>
class TrivialSPSSequenceSerialization<SPSTuple<SPSKeyTagT, SPSValueTagT>,
std::unordered_map<K, V>> {
public:
static constexpr bool available = true;
};
/// Trivial SPSSequence<SPSTuple<SPSKey, SPSValue>> -> std::unordered_map<K, V>
/// deserialization.
template <typename SPSKeyTagT, typename SPSValueTagT, typename K, typename V>
class TrivialSPSSequenceDeserialization<SPSTuple<SPSKeyTagT, SPSValueTagT>,
std::unordered_map<K, V>> {
public:
static constexpr bool available = true;
using element_type = std::pair<K, V>;
static void reserve(std::unordered_map<K, V> &M, uint64_t Size) {
M.reserve(Size);
}
static bool append(std::unordered_map<K, V> &M, element_type E) {
return M.insert(std::move(E)).second;
}
};
/// 'Trivial' sequence serialization: Sequence is serialized as a uint64_t size
/// followed by a for-earch loop over the elements of the sequence to serialize
/// each of them.
template <typename SPSElementTagT, typename SequenceT>
class SPSSerializationTraits<SPSSequence<SPSElementTagT>, SequenceT,
std::enable_if_t<TrivialSPSSequenceSerialization<
SPSElementTagT, SequenceT>::available>> {
public:
static size_t size(const SequenceT &S) {
size_t Size = SPSArgList<uint64_t>::size(static_cast<uint64_t>(S.size()));
for (const auto &E : S)
Size += SPSArgList<SPSElementTagT>::size(E);
return Size;
}
static bool serialize(SPSOutputBuffer &OB, const SequenceT &S) {
if (!SPSArgList<uint64_t>::serialize(OB, static_cast<uint64_t>(S.size())))
return false;
for (const auto &E : S)
if (!SPSArgList<SPSElementTagT>::serialize(OB, E))
return false;
return true;
}
static bool deserialize(SPSInputBuffer &IB, SequenceT &S) {
using TBSD = TrivialSPSSequenceDeserialization<SPSElementTagT, SequenceT>;
uint64_t Size;
if (!SPSArgList<uint64_t>::deserialize(IB, Size))
return false;
TBSD::reserve(S, Size);
for (size_t I = 0; I != Size; ++I) {
typename TBSD::element_type E;
if (!SPSArgList<SPSElementTagT>::deserialize(IB, E))
return false;
if (!TBSD::append(S, std::move(E)))
return false;
}
return true;
}
};
/// Trivial serialization / deserialization for span<char>
template <> class SPSSerializationTraits<SPSSequence<char>, span<const char>> {
public:
static size_t size(const span<const char> &S) {
return SPSArgList<uint64_t>::size(static_cast<uint64_t>(S.size())) +
S.size();
}
static bool serialize(SPSOutputBuffer &OB, const span<const char> &S) {
if (!SPSArgList<uint64_t>::serialize(OB, static_cast<uint64_t>(S.size())))
return false;
return OB.write(S.data(), S.size());
}
static bool deserialize(SPSInputBuffer &IB, span<const char> &S) {
uint64_t Size;
if (!SPSArgList<uint64_t>::deserialize(IB, Size))
return false;
S = span<const char>(IB.data(), Size);
return IB.skip(Size);
}
};
/// SPSTuple serialization for std::tuple.
template <typename... SPSTagTs, typename... Ts>
class SPSSerializationTraits<SPSTuple<SPSTagTs...>, std::tuple<Ts...>> {
private:
using TupleArgList = typename SPSTuple<SPSTagTs...>::AsArgList;
using ArgIndices = std::make_index_sequence<sizeof...(Ts)>;
template <std::size_t... I>
static size_t size(const std::tuple<Ts...> &T, std::index_sequence<I...>) {
return TupleArgList::size(std::get<I>(T)...);
}
template <std::size_t... I>
static bool serialize(SPSOutputBuffer &OB, const std::tuple<Ts...> &T,
std::index_sequence<I...>) {
return TupleArgList::serialize(OB, std::get<I>(T)...);
}
template <std::size_t... I>
static bool deserialize(SPSInputBuffer &IB, std::tuple<Ts...> &T,
std::index_sequence<I...>) {
return TupleArgList::deserialize(IB, std::get<I>(T)...);
}
public:
static size_t size(const std::tuple<Ts...> &T) {
return size(T, ArgIndices{});
}
static bool serialize(SPSOutputBuffer &OB, const std::tuple<Ts...> &T) {
return serialize(OB, T, ArgIndices{});
}
static bool deserialize(SPSInputBuffer &IB, std::tuple<Ts...> &T) {
return deserialize(IB, T, ArgIndices{});
}
};
/// SPSTuple serialization for std::pair.
template <typename SPSTagT1, typename SPSTagT2, typename T1, typename T2>
class SPSSerializationTraits<SPSTuple<SPSTagT1, SPSTagT2>, std::pair<T1, T2>> {
public:
static size_t size(const std::pair<T1, T2> &P) {
return SPSArgList<SPSTagT1>::size(P.first) +
SPSArgList<SPSTagT2>::size(P.second);
}
static bool serialize(SPSOutputBuffer &OB, const std::pair<T1, T2> &P) {
return SPSArgList<SPSTagT1>::serialize(OB, P.first) &&
SPSArgList<SPSTagT2>::serialize(OB, P.second);
}
static bool deserialize(SPSInputBuffer &IB, std::pair<T1, T2> &P) {
return SPSArgList<SPSTagT1>::deserialize(IB, P.first) &&
SPSArgList<SPSTagT2>::deserialize(IB, P.second);
}
};
/// SPSOptional serialization for std::optional.
template <typename SPSTagT, typename T>
class SPSSerializationTraits<SPSOptional<SPSTagT>, std::optional<T>> {
public:
static size_t size(const std::optional<T> &Value) {
size_t Size = SPSArgList<bool>::size(!!Value);
if (Value)
Size += SPSArgList<SPSTagT>::size(*Value);
return Size;
}
static bool serialize(SPSOutputBuffer &OB, const std::optional<T> &Value) {
if (!SPSArgList<bool>::serialize(OB, !!Value))
return false;
if (Value)
return SPSArgList<SPSTagT>::serialize(OB, *Value);
return true;
}
static bool deserialize(SPSInputBuffer &IB, std::optional<T> &Value) {
bool HasValue;
if (!SPSArgList<bool>::deserialize(IB, HasValue))
return false;
if (HasValue) {
Value = T();
return SPSArgList<SPSTagT>::deserialize(IB, *Value);
} else
Value = std::optional<T>();
return true;
}
};
/// Serialization for string_views.
///
/// Serialization is as for regular strings. Deserialization points directly
/// into the blob.
template <> class SPSSerializationTraits<SPSString, std::string_view> {
public:
static size_t size(const std::string_view &S) {
return SPSArgList<uint64_t>::size(static_cast<uint64_t>(S.size())) +
S.size();
}
static bool serialize(SPSOutputBuffer &OB, const std::string_view &S) {
if (!SPSArgList<uint64_t>::serialize(OB, static_cast<uint64_t>(S.size())))
return false;
return OB.write(S.data(), S.size());
}
static bool deserialize(SPSInputBuffer &IB, std::string_view &S) {
const char *Data = nullptr;
uint64_t Size;
if (!SPSArgList<uint64_t>::deserialize(IB, Size))
return false;
if (Size > std::numeric_limits<size_t>::max())
return false;
Data = IB.data();
if (!IB.skip(Size))
return false;
S = {Data, static_cast<size_t>(Size)};
return true;
}
};
/// SPS tag type for errors.
class SPSError;
/// SPS tag type for expecteds, which are either a T or a string representing
/// an error.
template <typename SPSTagT> class SPSExpected;
namespace detail {
/// Helper type for serializing Errors.
///
/// llvm::Errors are move-only, and not inspectable except by consuming them.
/// This makes them unsuitable for direct serialization via
/// SPSSerializationTraits, which needs to inspect values twice (once to
/// determine the amount of space to reserve, and then again to serialize).
///
/// The SPSSerializableError type is a helper that can be
/// constructed from an llvm::Error, but inspected more than once.
struct SPSSerializableError {
bool HasError = false;
std::string ErrMsg;
};
/// Helper type for serializing Expected<T>s.
///
/// See SPSSerializableError for more details.
///
// FIXME: Use std::variant for storage once we have c++17.
template <typename T> struct SPSSerializableExpected {
bool HasValue = false;
T Value{};
std::string ErrMsg;
};
inline SPSSerializableError toSPSSerializable(Error Err) {
if (Err)
return {true, toString(std::move(Err))};
return {false, {}};
}
inline Error fromSPSSerializable(SPSSerializableError BSE) {
if (BSE.HasError)
return make_error<StringError>(BSE.ErrMsg);
return Error::success();
}
template <typename T>
SPSSerializableExpected<T> toSPSSerializable(Expected<T> E) {
if (E)
return {true, std::move(*E), {}};
else
return {false, {}, toString(E.takeError())};
}
template <typename T>
Expected<T> fromSPSSerializable(SPSSerializableExpected<T> BSE) {
if (BSE.HasValue)
return std::move(BSE.Value);
else
return make_error<StringError>(BSE.ErrMsg);
}
} // end namespace detail
/// Serialize to a SPSError from a detail::SPSSerializableError.
template <>
class SPSSerializationTraits<SPSError, detail::SPSSerializableError> {
public:
static size_t size(const detail::SPSSerializableError &BSE) {
size_t Size = SPSArgList<bool>::size(BSE.HasError);
if (BSE.HasError)
Size += SPSArgList<SPSString>::size(BSE.ErrMsg);
return Size;
}
static bool serialize(SPSOutputBuffer &OB,
const detail::SPSSerializableError &BSE) {
if (!SPSArgList<bool>::serialize(OB, BSE.HasError))
return false;
if (BSE.HasError)
if (!SPSArgList<SPSString>::serialize(OB, BSE.ErrMsg))
return false;
return true;
}
static bool deserialize(SPSInputBuffer &IB,
detail::SPSSerializableError &BSE) {
if (!SPSArgList<bool>::deserialize(IB, BSE.HasError))
return false;
if (!BSE.HasError)
return true;
return SPSArgList<SPSString>::deserialize(IB, BSE.ErrMsg);
}
};
/// Serialize to a SPSExpected<SPSTagT> from a
/// detail::SPSSerializableExpected<T>.
template <typename SPSTagT, typename T>
class SPSSerializationTraits<SPSExpected<SPSTagT>,
detail::SPSSerializableExpected<T>> {
public:
static size_t size(const detail::SPSSerializableExpected<T> &BSE) {
size_t Size = SPSArgList<bool>::size(BSE.HasValue);
if (BSE.HasValue)
Size += SPSArgList<SPSTagT>::size(BSE.Value);
else
Size += SPSArgList<SPSString>::size(BSE.ErrMsg);
return Size;
}
static bool serialize(SPSOutputBuffer &OB,
const detail::SPSSerializableExpected<T> &BSE) {
if (!SPSArgList<bool>::serialize(OB, BSE.HasValue))
return false;
if (BSE.HasValue)
return SPSArgList<SPSTagT>::serialize(OB, BSE.Value);
return SPSArgList<SPSString>::serialize(OB, BSE.ErrMsg);
}
static bool deserialize(SPSInputBuffer &IB,
detail::SPSSerializableExpected<T> &BSE) {
if (!SPSArgList<bool>::deserialize(IB, BSE.HasValue))
return false;
if (BSE.HasValue)
return SPSArgList<SPSTagT>::deserialize(IB, BSE.Value);
return SPSArgList<SPSString>::deserialize(IB, BSE.ErrMsg);
}
};
/// Serialize to a SPSExpected<SPSTagT> from a detail::SPSSerializableError.
template <typename SPSTagT>
class SPSSerializationTraits<SPSExpected<SPSTagT>,
detail::SPSSerializableError> {
public:
static size_t size(const detail::SPSSerializableError &BSE) {
assert(BSE.HasError && "Cannot serialize expected from a success value");
return SPSArgList<bool>::size(false) +
SPSArgList<SPSString>::size(BSE.ErrMsg);
}
static bool serialize(SPSOutputBuffer &OB,
const detail::SPSSerializableError &BSE) {
assert(BSE.HasError && "Cannot serialize expected from a success value");
if (!SPSArgList<bool>::serialize(OB, false))
return false;
return SPSArgList<SPSString>::serialize(OB, BSE.ErrMsg);
}
};
/// Serialize to a SPSExpected<SPSTagT> from a T.
template <typename SPSTagT, typename T>
class SPSSerializationTraits<SPSExpected<SPSTagT>, T> {
public:
static size_t size(const T &Value) {
return SPSArgList<bool>::size(true) + SPSArgList<SPSTagT>::size(Value);
}
static bool serialize(SPSOutputBuffer &OB, const T &Value) {
if (!SPSArgList<bool>::serialize(OB, true))
return false;
return SPSArgList<SPSTagT>::serialize(Value);
}
};
} // end namespace __orc_rt
#endif // ORC_RT_SIMPLE_PACKED_SERIALIZATION_H