#pragma once #include #include #include #include #include "Enum.h" #include "Struct.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringRef.h" namespace clice::binary { namespace impl { template struct tuple_uniuqe { using type = Tuple; }; /// Uniuqe the types in the tuple. template using tuple_uniuqe_t = typename tuple_uniuqe::type; template requires (!std::is_same_v && ...) struct tuple_uniuqe> { using type = decltype(std::tuple_cat(std::declval>(), std::declval>>())); }; template requires (std::is_same_v || ...) struct tuple_uniuqe> { using type = tuple_uniuqe_t>; }; template struct array { uint32_t offset; uint32_t size; }; using string = array; /// Check whether a type can be directly binarized. template constexpr inline bool is_directly_binarizable_v = false; template requires (std::is_integral_v || refl::reflectable_enum) constexpr inline bool is_directly_binarizable_v = true; template constexpr inline bool is_directly_binarizable_v = refl::member_types::apply( []() { return (is_directly_binarizable_v && ...); }); template struct binarify; /// Transform a type to its binary representation. All `std::string` /// will be transformed to `string`. All `std::vector` will be /// transformed to `array`. template using binarify_t = typename binarify::type; /// For types that can be directly binarized, the binary representation /// is the same as the original type. template requires (is_directly_binarizable_v) struct binarify { using type = T; }; template <> struct binarify { using type = string; }; template struct binarify> { using type = array; }; template struct binarify> { using type = std::tuple...>; }; /// For reflectable struct, transform it recursively. template requires (refl::reflectable_struct && !is_directly_binarizable_v) struct binarify : binarify::to_tuple> {}; /// A section in the binary data. template struct Section { /// Current count of elements. uint32_t count = 0; /// Total count of elements in the section. uint32_t total = 0; /// Offset of the section. uint32_t offset = 0; }; template struct layout; /// Get the binary layout of a type. Make sure every type in the /// layout is unique. template using layout_t = tuple_uniuqe_t::type>; template requires (is_directly_binarizable_v) struct layout { using type = std::tuple<>; }; template <> struct layout { using type = std::tuple>; }; /// Every time we encounter a `std::vector`, we will add a `section`. template struct layout> { using type = decltype(std::tuple_cat(std::declval>>(), std::declval>())); }; template struct layout> { using type = decltype(std::tuple_cat(std::declval>()...)); }; /// For reflectable struct, recursively get the layout. template requires (refl::reflectable_struct && !is_directly_binarizable_v) struct layout { using type = layout_t::to_tuple>; }; template struct Packer { /// The layout of the binary data. layout_t layout = {}; /// The total size of the binary data. uint32_t size = 0; /// The buffer to store the binary data. char* buffer = nullptr; /// Recursively traverse the object and calculate the size of each section. template void init(const Object& object) { if constexpr(std::same_as) { std::get>(layout).total += object.size() + 1; } else if constexpr(requires { typename Object::value_type; }) { std::get>(layout).total += object.size(); for(const auto& element: object) { init(element); } } else if constexpr(refl::reflectable_struct) { refl::foreach(object, [&](auto, auto& field) { init(field); }); } } /// Write the object to the buffer and return the binary representation. template auto write(const Object& object) { if constexpr(is_directly_binarizable_v && !refl::reflectable_struct) { return object; } else if constexpr(std::same_as) { auto& section = std::get>(layout); uint32_t size = object.size(); uint32_t offset = section.offset + section.count; section.count += size + 1; std::memcpy(buffer + offset, object.data(), size); buffer[offset + size] = '\0'; return string{offset, size}; } else if constexpr(requires { typename Object::value_type; }) { using V = typename Object::value_type; auto& section = std::get>(layout); uint32_t size = object.size(); uint32_t offset = section.offset + section.count * sizeof(binarify_t); section.count += size; for(std::size_t i = 0; i < size; ++i) { ::new (buffer + offset + i * sizeof(binarify_t)) auto{write(object[i])}; } return array{offset, size}; } else if constexpr(refl::reflectable_struct) { std::array)> buffer; std::memset(buffer.data(), 0, sizeof(buffer)); binarify_t result; refl::foreach(result, object, [&](auto& lhs, auto& rhs) { auto offset = reinterpret_cast(&lhs) - reinterpret_cast(&result); ::new (buffer.data() + offset) auto{write(rhs)}; }); return buffer; } else { static_assert(dependent_false, "Unsupported type."); } } char* pack(const auto& object) { /// First initialize the layout. init(object); /// Calculate the total size of the binary data and /// the offset of each section. size = sizeof(binarify_t); auto try_each = [&](auto, Section& field) { static_assert(alignof(binarify_t) <= 8, "Alignment not supported."); /// Make sure each section is aligned to 8 bytes. if(size % 8 != 0) { size += 8 - size % 8; } field.offset = size; size += field.total * sizeof(binarify_t); }; refl::foreach(layout, try_each); /// Allocate the buffer and write the data to the buffer. buffer = static_cast(std::malloc(size)); /// Make sure the buffer is clean. So we can compare the result. /// Every padding in the struct should be filled with 0. std::memset(buffer, 0, size); /// Write the object to the buffer. auto result = write(object); std::memcpy(buffer, &result, sizeof(result)); return buffer; } }; } // namespace impl template struct fixed_string : std::array { template constexpr fixed_string(const char (&str)[M]) { for(std::size_t i = 0; i < N; ++i) { this->data()[i] = str[i]; } this->data()[N] = '\0'; } constexpr auto size() const { return N; } constexpr operator std::string_view () const { return {this->data(), N}; } }; template fixed_string(const char (&)[M]) -> fixed_string; /// A helper class to access the binary data. template struct Proxy { using underlying_type = impl::binarify_t; const void* base; const void* data; const auto& value() const { return *reinterpret_cast(data); } template auto get() const { return Proxy>{base, &std::get(value())}; } template auto get() const { constexpr auto& names = refl::member_names(); constexpr auto index = []() { for(std::size_t i = 0; i < names.size(); ++i) { if(names[i] == name) { return i; } } return names.size(); }(); return this->template get(); } auto as_string() const { auto [offset, size] = value(); return llvm::StringRef{reinterpret_cast(base) + offset, size}; } auto as_array() const { auto [offset, size] = value(); using U = impl::binarify_t; return llvm::ArrayRef{ reinterpret_cast(static_cast(base) + offset), size, }; } auto operator[] (std::size_t index) const { return Proxy{base, &as_array()[index]}; } auto size() const { return value().size; } auto operator->() const { return &value(); } operator const underlying_type& () const { return value(); } }; /// Binirize an object. template std::pair, size_t> binarify(const Object& object) { impl::Packer packer; auto buffer = packer.pack(object); return { Proxy{buffer, reinterpret_cast*>(buffer)}, packer.size }; } } // namespace clice::binary