1 //===--- simple_packed_serialization.h - simple serialization ---*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file is a part of the ORC runtime support library. 10 // 11 // The behavior of the utilities in this header must be synchronized with the 12 // behavior of the utilities in 13 // llvm/ExecutionEngine/Orc/Shared/WrapperFunctionUtils.h. 14 // 15 // The Simple Packed Serialization (SPS) utilities are used to generate 16 // argument and return buffers for wrapper functions using the following 17 // serialization scheme: 18 // 19 // Primitives: 20 // bool, char, int8_t, uint8_t -- Two's complement 8-bit (0=false, 1=true) 21 // int16_t, uint16_t -- Two's complement 16-bit little endian 22 // int32_t, uint32_t -- Two's complement 32-bit little endian 23 // int64_t, int64_t -- Two's complement 64-bit little endian 24 // 25 // Sequence<T>: 26 // Serialized as the sequence length (as a uint64_t) followed by the 27 // serialization of each of the elements without padding. 28 // 29 // Tuple<T1, ..., TN>: 30 // Serialized as each of the element types from T1 to TN without padding. 31 // 32 //===----------------------------------------------------------------------===// 33 34 #ifndef ORC_RT_SIMPLE_PACKED_SERIALIZATION_H 35 #define ORC_RT_SIMPLE_PACKED_SERIALIZATION_H 36 37 #include "adt.h" 38 #include "endianness.h" 39 #include "error.h" 40 #include "stl_extras.h" 41 42 #include <optional> 43 #include <string> 44 #include <string_view> 45 #include <tuple> 46 #include <type_traits> 47 #include <unordered_map> 48 #include <utility> 49 #include <vector> 50 51 namespace __orc_rt { 52 53 /// Output char buffer with overflow check. 54 class SPSOutputBuffer { 55 public: 56 SPSOutputBuffer(char *Buffer, size_t Remaining) 57 : Buffer(Buffer), Remaining(Remaining) {} 58 bool write(const char *Data, size_t Size) { 59 if (Size > Remaining) 60 return false; 61 memcpy(Buffer, Data, Size); 62 Buffer += Size; 63 Remaining -= Size; 64 return true; 65 } 66 67 private: 68 char *Buffer = nullptr; 69 size_t Remaining = 0; 70 }; 71 72 /// Input char buffer with underflow check. 73 class SPSInputBuffer { 74 public: 75 SPSInputBuffer() = default; 76 SPSInputBuffer(const char *Buffer, size_t Remaining) 77 : Buffer(Buffer), Remaining(Remaining) {} 78 bool read(char *Data, size_t Size) { 79 if (Size > Remaining) 80 return false; 81 memcpy(Data, Buffer, Size); 82 Buffer += Size; 83 Remaining -= Size; 84 return true; 85 } 86 87 const char *data() const { return Buffer; } 88 bool skip(size_t Size) { 89 if (Size > Remaining) 90 return false; 91 Buffer += Size; 92 Remaining -= Size; 93 return true; 94 } 95 96 private: 97 const char *Buffer = nullptr; 98 size_t Remaining = 0; 99 }; 100 101 /// Specialize to describe how to serialize/deserialize to/from the given 102 /// concrete type. 103 template <typename SPSTagT, typename ConcreteT, typename _ = void> 104 class SPSSerializationTraits; 105 106 /// A utility class for serializing to a blob from a variadic list. 107 template <typename... ArgTs> class SPSArgList; 108 109 // Empty list specialization for SPSArgList. 110 template <> class SPSArgList<> { 111 public: 112 static size_t size() { return 0; } 113 114 static bool serialize(SPSOutputBuffer &OB) { return true; } 115 static bool deserialize(SPSInputBuffer &IB) { return true; } 116 }; 117 118 // Non-empty list specialization for SPSArgList. 119 template <typename SPSTagT, typename... SPSTagTs> 120 class SPSArgList<SPSTagT, SPSTagTs...> { 121 public: 122 template <typename ArgT, typename... ArgTs> 123 static size_t size(const ArgT &Arg, const ArgTs &...Args) { 124 return SPSSerializationTraits<SPSTagT, ArgT>::size(Arg) + 125 SPSArgList<SPSTagTs...>::size(Args...); 126 } 127 128 template <typename ArgT, typename... ArgTs> 129 static bool serialize(SPSOutputBuffer &OB, const ArgT &Arg, 130 const ArgTs &...Args) { 131 return SPSSerializationTraits<SPSTagT, ArgT>::serialize(OB, Arg) && 132 SPSArgList<SPSTagTs...>::serialize(OB, Args...); 133 } 134 135 template <typename ArgT, typename... ArgTs> 136 static bool deserialize(SPSInputBuffer &IB, ArgT &Arg, ArgTs &...Args) { 137 return SPSSerializationTraits<SPSTagT, ArgT>::deserialize(IB, Arg) && 138 SPSArgList<SPSTagTs...>::deserialize(IB, Args...); 139 } 140 }; 141 142 /// SPS serialization for integral types, bool, and char. 143 template <typename SPSTagT> 144 class SPSSerializationTraits< 145 SPSTagT, SPSTagT, 146 std::enable_if_t<std::is_same<SPSTagT, bool>::value || 147 std::is_same<SPSTagT, char>::value || 148 std::is_same<SPSTagT, int8_t>::value || 149 std::is_same<SPSTagT, int16_t>::value || 150 std::is_same<SPSTagT, int32_t>::value || 151 std::is_same<SPSTagT, int64_t>::value || 152 std::is_same<SPSTagT, uint8_t>::value || 153 std::is_same<SPSTagT, uint16_t>::value || 154 std::is_same<SPSTagT, uint32_t>::value || 155 std::is_same<SPSTagT, uint64_t>::value>> { 156 public: 157 static size_t size(const SPSTagT &Value) { return sizeof(SPSTagT); } 158 159 static bool serialize(SPSOutputBuffer &OB, const SPSTagT &Value) { 160 SPSTagT Tmp = Value; 161 if (IsBigEndianHost) 162 swapByteOrder(Tmp); 163 return OB.write(reinterpret_cast<const char *>(&Tmp), sizeof(Tmp)); 164 } 165 166 static bool deserialize(SPSInputBuffer &IB, SPSTagT &Value) { 167 SPSTagT Tmp; 168 if (!IB.read(reinterpret_cast<char *>(&Tmp), sizeof(Tmp))) 169 return false; 170 if (IsBigEndianHost) 171 swapByteOrder(Tmp); 172 Value = Tmp; 173 return true; 174 } 175 }; 176 177 /// Any empty placeholder suitable as a substitute for void when deserializing 178 class SPSEmpty {}; 179 180 /// Represents an address in the executor. 181 class SPSExecutorAddr {}; 182 183 /// SPS tag type for tuples. 184 /// 185 /// A blob tuple should be serialized by serializing each of the elements in 186 /// sequence. 187 template <typename... SPSTagTs> class SPSTuple { 188 public: 189 /// Convenience typedef of the corresponding arg list. 190 typedef SPSArgList<SPSTagTs...> AsArgList; 191 }; 192 193 /// SPS tag type for optionals. 194 /// 195 /// SPSOptionals should be serialized as a bool with true indicating that an 196 /// SPSTagT value is present, and false indicating that there is no value. 197 /// If the boolean is true then the serialized SPSTagT will follow immediately 198 /// after it. 199 template <typename SPSTagT> class SPSOptional {}; 200 201 /// SPS tag type for sequences. 202 /// 203 /// SPSSequences should be serialized as a uint64_t sequence length, 204 /// followed by the serialization of each of the elements. 205 template <typename SPSElementTagT> class SPSSequence; 206 207 /// SPS tag type for strings, which are equivalent to sequences of chars. 208 using SPSString = SPSSequence<char>; 209 210 /// SPS tag type for maps. 211 /// 212 /// SPS maps are just sequences of (Key, Value) tuples. 213 template <typename SPSTagT1, typename SPSTagT2> 214 using SPSMap = SPSSequence<SPSTuple<SPSTagT1, SPSTagT2>>; 215 216 /// Serialization for SPSEmpty type. 217 template <> class SPSSerializationTraits<SPSEmpty, SPSEmpty> { 218 public: 219 static size_t size(const SPSEmpty &EP) { return 0; } 220 static bool serialize(SPSOutputBuffer &OB, const SPSEmpty &BE) { 221 return true; 222 } 223 static bool deserialize(SPSInputBuffer &IB, SPSEmpty &BE) { return true; } 224 }; 225 226 /// Specialize this to implement 'trivial' sequence serialization for 227 /// a concrete sequence type. 228 /// 229 /// Trivial sequence serialization uses the sequence's 'size' member to get the 230 /// length of the sequence, and uses a range-based for loop to iterate over the 231 /// elements. 232 /// 233 /// Specializing this template class means that you do not need to provide a 234 /// specialization of SPSSerializationTraits for your type. 235 template <typename SPSElementTagT, typename ConcreteSequenceT> 236 class TrivialSPSSequenceSerialization { 237 public: 238 static constexpr bool available = false; 239 }; 240 241 /// Specialize this to implement 'trivial' sequence deserialization for 242 /// a concrete sequence type. 243 /// 244 /// Trivial deserialization calls a static 'reserve(SequenceT&)' method on your 245 /// specialization (you must implement this) to reserve space, and then calls 246 /// a static 'append(SequenceT&, ElementT&) method to append each of the 247 /// deserialized elements. 248 /// 249 /// Specializing this template class means that you do not need to provide a 250 /// specialization of SPSSerializationTraits for your type. 251 template <typename SPSElementTagT, typename ConcreteSequenceT> 252 class TrivialSPSSequenceDeserialization { 253 public: 254 static constexpr bool available = false; 255 }; 256 257 /// Trivial std::string -> SPSSequence<char> serialization. 258 template <> class TrivialSPSSequenceSerialization<char, std::string> { 259 public: 260 static constexpr bool available = true; 261 }; 262 263 /// Trivial SPSSequence<char> -> std::string deserialization. 264 template <> class TrivialSPSSequenceDeserialization<char, std::string> { 265 public: 266 static constexpr bool available = true; 267 268 using element_type = char; 269 270 static void reserve(std::string &S, uint64_t Size) { S.reserve(Size); } 271 static bool append(std::string &S, char C) { 272 S.push_back(C); 273 return true; 274 } 275 }; 276 277 /// Trivial std::vector<T> -> SPSSequence<SPSElementTagT> serialization. 278 template <typename SPSElementTagT, typename T> 279 class TrivialSPSSequenceSerialization<SPSElementTagT, std::vector<T>> { 280 public: 281 static constexpr bool available = true; 282 }; 283 284 /// Trivial SPSSequence<SPSElementTagT> -> std::vector<T> deserialization. 285 template <typename SPSElementTagT, typename T> 286 class TrivialSPSSequenceDeserialization<SPSElementTagT, std::vector<T>> { 287 public: 288 static constexpr bool available = true; 289 290 using element_type = typename std::vector<T>::value_type; 291 292 static void reserve(std::vector<T> &V, uint64_t Size) { V.reserve(Size); } 293 static bool append(std::vector<T> &V, T E) { 294 V.push_back(std::move(E)); 295 return true; 296 } 297 }; 298 299 /// Trivial std::unordered_map<K, V> -> SPSSequence<SPSTuple<SPSKey, SPSValue>> 300 /// serialization. 301 template <typename SPSKeyTagT, typename SPSValueTagT, typename K, typename V> 302 class TrivialSPSSequenceSerialization<SPSTuple<SPSKeyTagT, SPSValueTagT>, 303 std::unordered_map<K, V>> { 304 public: 305 static constexpr bool available = true; 306 }; 307 308 /// Trivial SPSSequence<SPSTuple<SPSKey, SPSValue>> -> std::unordered_map<K, V> 309 /// deserialization. 310 template <typename SPSKeyTagT, typename SPSValueTagT, typename K, typename V> 311 class TrivialSPSSequenceDeserialization<SPSTuple<SPSKeyTagT, SPSValueTagT>, 312 std::unordered_map<K, V>> { 313 public: 314 static constexpr bool available = true; 315 316 using element_type = std::pair<K, V>; 317 318 static void reserve(std::unordered_map<K, V> &M, uint64_t Size) { 319 M.reserve(Size); 320 } 321 static bool append(std::unordered_map<K, V> &M, element_type E) { 322 return M.insert(std::move(E)).second; 323 } 324 }; 325 326 /// 'Trivial' sequence serialization: Sequence is serialized as a uint64_t size 327 /// followed by a for-earch loop over the elements of the sequence to serialize 328 /// each of them. 329 template <typename SPSElementTagT, typename SequenceT> 330 class SPSSerializationTraits<SPSSequence<SPSElementTagT>, SequenceT, 331 std::enable_if_t<TrivialSPSSequenceSerialization< 332 SPSElementTagT, SequenceT>::available>> { 333 public: 334 static size_t size(const SequenceT &S) { 335 size_t Size = SPSArgList<uint64_t>::size(static_cast<uint64_t>(S.size())); 336 for (const auto &E : S) 337 Size += SPSArgList<SPSElementTagT>::size(E); 338 return Size; 339 } 340 341 static bool serialize(SPSOutputBuffer &OB, const SequenceT &S) { 342 if (!SPSArgList<uint64_t>::serialize(OB, static_cast<uint64_t>(S.size()))) 343 return false; 344 for (const auto &E : S) 345 if (!SPSArgList<SPSElementTagT>::serialize(OB, E)) 346 return false; 347 return true; 348 } 349 350 static bool deserialize(SPSInputBuffer &IB, SequenceT &S) { 351 using TBSD = TrivialSPSSequenceDeserialization<SPSElementTagT, SequenceT>; 352 uint64_t Size; 353 if (!SPSArgList<uint64_t>::deserialize(IB, Size)) 354 return false; 355 TBSD::reserve(S, Size); 356 for (size_t I = 0; I != Size; ++I) { 357 typename TBSD::element_type E; 358 if (!SPSArgList<SPSElementTagT>::deserialize(IB, E)) 359 return false; 360 if (!TBSD::append(S, std::move(E))) 361 return false; 362 } 363 return true; 364 } 365 }; 366 367 /// Trivial serialization / deserialization for span<char> 368 template <> class SPSSerializationTraits<SPSSequence<char>, span<const char>> { 369 public: 370 static size_t size(const span<const char> &S) { 371 return SPSArgList<uint64_t>::size(static_cast<uint64_t>(S.size())) + 372 S.size(); 373 } 374 static bool serialize(SPSOutputBuffer &OB, const span<const char> &S) { 375 if (!SPSArgList<uint64_t>::serialize(OB, static_cast<uint64_t>(S.size()))) 376 return false; 377 return OB.write(S.data(), S.size()); 378 } 379 static bool deserialize(SPSInputBuffer &IB, span<const char> &S) { 380 uint64_t Size; 381 if (!SPSArgList<uint64_t>::deserialize(IB, Size)) 382 return false; 383 S = span<const char>(IB.data(), Size); 384 return IB.skip(Size); 385 } 386 }; 387 388 /// SPSTuple serialization for std::pair. 389 template <typename SPSTagT1, typename SPSTagT2, typename T1, typename T2> 390 class SPSSerializationTraits<SPSTuple<SPSTagT1, SPSTagT2>, std::pair<T1, T2>> { 391 public: 392 static size_t size(const std::pair<T1, T2> &P) { 393 return SPSArgList<SPSTagT1>::size(P.first) + 394 SPSArgList<SPSTagT2>::size(P.second); 395 } 396 397 static bool serialize(SPSOutputBuffer &OB, const std::pair<T1, T2> &P) { 398 return SPSArgList<SPSTagT1>::serialize(OB, P.first) && 399 SPSArgList<SPSTagT2>::serialize(OB, P.second); 400 } 401 402 static bool deserialize(SPSInputBuffer &IB, std::pair<T1, T2> &P) { 403 return SPSArgList<SPSTagT1>::deserialize(IB, P.first) && 404 SPSArgList<SPSTagT2>::deserialize(IB, P.second); 405 } 406 }; 407 408 /// SPSOptional serialization for std::optional. 409 template <typename SPSTagT, typename T> 410 class SPSSerializationTraits<SPSOptional<SPSTagT>, std::optional<T>> { 411 public: 412 static size_t size(const std::optional<T> &Value) { 413 size_t Size = SPSArgList<bool>::size(!!Value); 414 if (Value) 415 Size += SPSArgList<SPSTagT>::size(*Value); 416 return Size; 417 } 418 419 static bool serialize(SPSOutputBuffer &OB, const std::optional<T> &Value) { 420 if (!SPSArgList<bool>::serialize(OB, !!Value)) 421 return false; 422 if (Value) 423 return SPSArgList<SPSTagT>::serialize(OB, *Value); 424 return true; 425 } 426 427 static bool deserialize(SPSInputBuffer &IB, std::optional<T> &Value) { 428 bool HasValue; 429 if (!SPSArgList<bool>::deserialize(IB, HasValue)) 430 return false; 431 if (HasValue) { 432 Value = T(); 433 return SPSArgList<SPSTagT>::deserialize(IB, *Value); 434 } else 435 Value = std::optional<T>(); 436 return true; 437 } 438 }; 439 440 /// Serialization for string_views. 441 /// 442 /// Serialization is as for regular strings. Deserialization points directly 443 /// into the blob. 444 template <> class SPSSerializationTraits<SPSString, std::string_view> { 445 public: 446 static size_t size(const std::string_view &S) { 447 return SPSArgList<uint64_t>::size(static_cast<uint64_t>(S.size())) + 448 S.size(); 449 } 450 451 static bool serialize(SPSOutputBuffer &OB, const std::string_view &S) { 452 if (!SPSArgList<uint64_t>::serialize(OB, static_cast<uint64_t>(S.size()))) 453 return false; 454 return OB.write(S.data(), S.size()); 455 } 456 457 static bool deserialize(SPSInputBuffer &IB, std::string_view &S) { 458 const char *Data = nullptr; 459 uint64_t Size; 460 if (!SPSArgList<uint64_t>::deserialize(IB, Size)) 461 return false; 462 if (Size > std::numeric_limits<size_t>::max()) 463 return false; 464 Data = IB.data(); 465 if (!IB.skip(Size)) 466 return false; 467 S = {Data, static_cast<size_t>(Size)}; 468 return true; 469 } 470 }; 471 472 /// SPS tag type for errors. 473 class SPSError; 474 475 /// SPS tag type for expecteds, which are either a T or a string representing 476 /// an error. 477 template <typename SPSTagT> class SPSExpected; 478 479 namespace detail { 480 481 /// Helper type for serializing Errors. 482 /// 483 /// llvm::Errors are move-only, and not inspectable except by consuming them. 484 /// This makes them unsuitable for direct serialization via 485 /// SPSSerializationTraits, which needs to inspect values twice (once to 486 /// determine the amount of space to reserve, and then again to serialize). 487 /// 488 /// The SPSSerializableError type is a helper that can be 489 /// constructed from an llvm::Error, but inspected more than once. 490 struct SPSSerializableError { 491 bool HasError = false; 492 std::string ErrMsg; 493 }; 494 495 /// Helper type for serializing Expected<T>s. 496 /// 497 /// See SPSSerializableError for more details. 498 /// 499 // FIXME: Use std::variant for storage once we have c++17. 500 template <typename T> struct SPSSerializableExpected { 501 bool HasValue = false; 502 T Value{}; 503 std::string ErrMsg; 504 }; 505 506 inline SPSSerializableError toSPSSerializable(Error Err) { 507 if (Err) 508 return {true, toString(std::move(Err))}; 509 return {false, {}}; 510 } 511 512 inline Error fromSPSSerializable(SPSSerializableError BSE) { 513 if (BSE.HasError) 514 return make_error<StringError>(BSE.ErrMsg); 515 return Error::success(); 516 } 517 518 template <typename T> 519 SPSSerializableExpected<T> toSPSSerializable(Expected<T> E) { 520 if (E) 521 return {true, std::move(*E), {}}; 522 else 523 return {false, {}, toString(E.takeError())}; 524 } 525 526 template <typename T> 527 Expected<T> fromSPSSerializable(SPSSerializableExpected<T> BSE) { 528 if (BSE.HasValue) 529 return std::move(BSE.Value); 530 else 531 return make_error<StringError>(BSE.ErrMsg); 532 } 533 534 } // end namespace detail 535 536 /// Serialize to a SPSError from a detail::SPSSerializableError. 537 template <> 538 class SPSSerializationTraits<SPSError, detail::SPSSerializableError> { 539 public: 540 static size_t size(const detail::SPSSerializableError &BSE) { 541 size_t Size = SPSArgList<bool>::size(BSE.HasError); 542 if (BSE.HasError) 543 Size += SPSArgList<SPSString>::size(BSE.ErrMsg); 544 return Size; 545 } 546 547 static bool serialize(SPSOutputBuffer &OB, 548 const detail::SPSSerializableError &BSE) { 549 if (!SPSArgList<bool>::serialize(OB, BSE.HasError)) 550 return false; 551 if (BSE.HasError) 552 if (!SPSArgList<SPSString>::serialize(OB, BSE.ErrMsg)) 553 return false; 554 return true; 555 } 556 557 static bool deserialize(SPSInputBuffer &IB, 558 detail::SPSSerializableError &BSE) { 559 if (!SPSArgList<bool>::deserialize(IB, BSE.HasError)) 560 return false; 561 562 if (!BSE.HasError) 563 return true; 564 565 return SPSArgList<SPSString>::deserialize(IB, BSE.ErrMsg); 566 } 567 }; 568 569 /// Serialize to a SPSExpected<SPSTagT> from a 570 /// detail::SPSSerializableExpected<T>. 571 template <typename SPSTagT, typename T> 572 class SPSSerializationTraits<SPSExpected<SPSTagT>, 573 detail::SPSSerializableExpected<T>> { 574 public: 575 static size_t size(const detail::SPSSerializableExpected<T> &BSE) { 576 size_t Size = SPSArgList<bool>::size(BSE.HasValue); 577 if (BSE.HasValue) 578 Size += SPSArgList<SPSTagT>::size(BSE.Value); 579 else 580 Size += SPSArgList<SPSString>::size(BSE.ErrMsg); 581 return Size; 582 } 583 584 static bool serialize(SPSOutputBuffer &OB, 585 const detail::SPSSerializableExpected<T> &BSE) { 586 if (!SPSArgList<bool>::serialize(OB, BSE.HasValue)) 587 return false; 588 589 if (BSE.HasValue) 590 return SPSArgList<SPSTagT>::serialize(OB, BSE.Value); 591 592 return SPSArgList<SPSString>::serialize(OB, BSE.ErrMsg); 593 } 594 595 static bool deserialize(SPSInputBuffer &IB, 596 detail::SPSSerializableExpected<T> &BSE) { 597 if (!SPSArgList<bool>::deserialize(IB, BSE.HasValue)) 598 return false; 599 600 if (BSE.HasValue) 601 return SPSArgList<SPSTagT>::deserialize(IB, BSE.Value); 602 603 return SPSArgList<SPSString>::deserialize(IB, BSE.ErrMsg); 604 } 605 }; 606 607 /// Serialize to a SPSExpected<SPSTagT> from a detail::SPSSerializableError. 608 template <typename SPSTagT> 609 class SPSSerializationTraits<SPSExpected<SPSTagT>, 610 detail::SPSSerializableError> { 611 public: 612 static size_t size(const detail::SPSSerializableError &BSE) { 613 assert(BSE.HasError && "Cannot serialize expected from a success value"); 614 return SPSArgList<bool>::size(false) + 615 SPSArgList<SPSString>::size(BSE.ErrMsg); 616 } 617 618 static bool serialize(SPSOutputBuffer &OB, 619 const detail::SPSSerializableError &BSE) { 620 assert(BSE.HasError && "Cannot serialize expected from a success value"); 621 if (!SPSArgList<bool>::serialize(OB, false)) 622 return false; 623 return SPSArgList<SPSString>::serialize(OB, BSE.ErrMsg); 624 } 625 }; 626 627 /// Serialize to a SPSExpected<SPSTagT> from a T. 628 template <typename SPSTagT, typename T> 629 class SPSSerializationTraits<SPSExpected<SPSTagT>, T> { 630 public: 631 static size_t size(const T &Value) { 632 return SPSArgList<bool>::size(true) + SPSArgList<SPSTagT>::size(Value); 633 } 634 635 static bool serialize(SPSOutputBuffer &OB, const T &Value) { 636 if (!SPSArgList<bool>::serialize(OB, true)) 637 return false; 638 return SPSArgList<SPSTagT>::serialize(Value); 639 } 640 }; 641 642 } // end namespace __orc_rt 643 644 #endif // ORC_RT_SIMPLE_PACKED_SERIALIZATION_H 645