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