#pragma once #include #include #include #include #include "Enum.h" #include "Struct.h" #include "Format.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/MemoryBuffer.h" namespace clice::binary { template struct array { /// The offset to the beginning of binary buffer. uint32_t offset; /// The size of array. uint32_t size; }; using string = array; /// Check whether a type can be directly binarized. template constexpr inline bool is_directly_binarizable_v = [] { if constexpr(std::is_integral_v || refl::reflectable_enum) { return true; } else if constexpr(refl::reflectable_struct) { return refl::member_types::apply( []() { return (is_directly_binarizable_v && ...); }); } else { return false; } }(); template consteval auto binarify(); template using binarify_t = typename decltype(binarify())::type; template consteval auto binarify() { if constexpr(is_directly_binarizable_v) { return identity(); } else if constexpr(std::is_same_v) { return identity(); } else if constexpr(is_specialization_of) { return identity>(); } else if constexpr(is_specialization_of) { return tuple_to_list_t::apply( [] { return identity...>>(); }); } else if constexpr(refl::reflectable_struct) { return refl::member_types::apply( [] { return identity...>>(); }); } else { static_assert(dependent_false, "unsupported type"); } } /// 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 consteval auto layout() { if constexpr(is_directly_binarizable_v) { return std::tuple<>(); } else if constexpr(std::is_same_v) { return std::tuple>(); } else if constexpr(is_specialization_of) { using V = typename T::value_type; if constexpr(std::is_same_v) { return std::tuple>(); } else { return std::tuple_cat(std::tuple>(), layout()); } } else if constexpr(is_specialization_of) { return tuple_to_list_t::apply( [] { return std::tuple_cat(layout()...); }); } else if constexpr(refl::reflectable_struct) { return refl::member_types::apply( [] { return std::tuple_cat(layout()...); }); } else { static_assert(dependent_false, "unsupported type"); } } /// Get the binary layout of a type. Make sure every type in the /// layout is unique. template using layout_t = tuple_uniuqe_t())>; 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. std::vector buffer; /// 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); }); } } template requires (is_directly_binarizable_v && !refl::reflectable_struct) Object write(const Object& object) { return object; } template requires (std::is_same_v) string write(const Object& object) { auto& section = std::get>(layout); uint32_t size = object.size(); uint32_t offset = section.offset + section.count; section.count += size + 1; std::memcpy(buffer.data() + offset, object.data(), size); buffer[offset + size] = '\0'; return string{offset, size}; } template requires (is_specialization_of) array write(const Object& object) { 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.data() + offset + i * sizeof(binarify_t)) auto{write(object[i])}; } return array{offset, size}; } template requires (refl::reflectable_struct) std::array)> write(const Object& object) { 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; } std::vector 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); /// Make sure the buffer is clean. So we can compare the result. /// Every padding in the struct should be filled with 0. buffer.resize(size, 0); /// Write the object to the buffer. auto result = write(object); std::memcpy(buffer.data(), &result, sizeof(result)); return std::move(buffer); } }; 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 = 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 = 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 auto serialize(const Object& object) { auto buffer = Packer().pack(object); auto proxy = Proxy{buffer.data(), buffer.data()}; return std::tuple(std::move(buffer), proxy); } template Object deserialize(Proxy proxy) { if constexpr(is_directly_binarizable_v) { return proxy.value(); } else if constexpr(std::is_same_v) { return proxy.as_string().str(); } else if constexpr(is_specialization_of) { Object result; for(std::size_t i = 0; i < proxy.size(); i++) { result.emplace_back(deserialize(proxy[i])); } return result; } else if constexpr(refl::reflectable_struct) { return [&](std::index_sequence) { return Object{deserialize(proxy.template get())...}; }(std::make_index_sequence()>()); } else { static_assert(dependent_false, ""); } } } // namespace clice::binary