1 //===-- C++ code generation from NamedFunctionDescriptors -----------------===//
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
7 //===----------------------------------------------------------------------===//
8 // This code is responsible for generating the "Implementation.cpp" file.
9 // The file is composed like this:
12 // 2. Using statements to help readability.
13 // 3. Source code for all the mem function implementations.
14 // 4. The function to retrieve all the function descriptors with their name.
15 // llvm::ArrayRef<NamedFunctionDescriptor> getFunctionDescriptors();
16 // 5. The functions for the benchmarking infrastructure:
17 // llvm::ArrayRef<MemcpyConfiguration> getMemcpyConfigurations();
18 // llvm::ArrayRef<MemcmpOrBcmpConfiguration> getMemcmpConfigurations();
19 // llvm::ArrayRef<MemcmpOrBcmpConfiguration> getBcmpConfigurations();
20 // llvm::ArrayRef<MemsetConfiguration> getMemsetConfigurations();
21 // llvm::ArrayRef<BzeroConfiguration> getBzeroConfigurations();
24 // Sections 3, 4 and 5 are handled by the following namespaces:
25 // - codegen::functions
26 // - codegen::descriptors
27 // - codegen::configurations
29 // The programming style is functionnal. In each of these namespace, the
30 // original `NamedFunctionDescriptor` object is turned into a different type. We
31 // make use of overloaded stream operators to format the resulting type into
32 // either a function, a descriptor or a configuration. The entry point of each
33 // namespace is the Serialize function.
35 // Note the code here is better understood by starting from the `Serialize`
36 // function at the end of the file.
38 #include "automemcpy/CodeGen.h"
40 #include <llvm/ADT/STLExtras.h>
41 #include <llvm/ADT/StringSet.h>
42 #include <llvm/Support/FormatVariadic.h>
43 #include <llvm/Support/raw_ostream.h>
48 namespace automemcpy
{
51 // The indentation string.
52 static constexpr StringRef kIndent
= " ";
54 // The codegen namespace handles the serialization of a NamedFunctionDescriptor
55 // into source code for the function, the descriptor and the configuration.
59 // This namespace turns a NamedFunctionDescriptor into an actual implementation.
60 // -----------------------------------------------------------------------------
62 // static void memcpy_0xB20D4702493C397E(char *__restrict dst,
63 // const char *__restrict src,
65 // using namespace __llvm_libc::x86;
66 // if(size == 0) return;
67 // if(size == 1) return copy<_1>(dst, src);
68 // if(size < 4) return copy<HeadTail<_2>>(dst, src, size);
69 // if(size < 8) return copy<HeadTail<_4>>(dst, src, size);
70 // if(size < 16) return copy<HeadTail<_8>>(dst, src, size);
71 // if(size < 32) return copy<HeadTail<_16>>(dst, src, size);
72 // return copy<Accelerator>(dst, src, size);
75 // The `Serialize` method turns a `NamedFunctionDescriptor` into a
76 // `FunctionImplementation` which holds all the information needed to produce
77 // the C++ source code.
79 // An Element with its size (e.g. `_16` in the example above).
83 // The case `if(size == 0)` is encoded as a the Zero type.
85 StringRef DefaultReturnValue
;
87 // An individual size `if(size == X)` is encoded as an Individual type.
92 // An overlap strategy is encoded as an Overlap type.
97 // A loop strategy is encoded as a Loop type.
102 // An aligned loop strategy is encoded as an AlignedLoop type.
106 ElementType Alignment
;
109 // The accelerator strategy.
113 // The Context stores data about the function type.
115 StringRef FunctionReturnType
; // e.g. void* or int
116 StringRef FunctionArgs
;
117 StringRef ElementOp
; // copy, three_way_compare, splat_set, ...
118 StringRef FixedSizeArgs
;
119 StringRef RuntimeSizeArgs
;
120 StringRef DefaultReturnValue
;
122 // A detailed representation of the function implementation mapped from the
123 // NamedFunctionDescriptor.
124 struct FunctionImplementation
{
127 std::vector
<Individual
> Individuals
;
128 std::vector
<Overlap
> Overlaps
;
129 std::optional
<Loop
> Loop
;
130 std::optional
<AlignedLoop
> AlignedLoop
;
131 std::optional
<Accelerator
> Accelerator
;
132 ElementTypeClass ElementClass
;
135 // Returns the Context for each FunctionType.
136 static Context
getCtx(FunctionType FT
) {
138 case FunctionType::MEMCPY
:
140 "(char *__restrict dst, const char *__restrict src, size_t size)",
145 case FunctionType::MEMCMP
:
147 "(const char * lhs, const char * rhs, size_t size)",
152 case FunctionType::MEMSET
:
154 "(char * dst, int value, size_t size)",
157 "(dst, value, size)",
159 case FunctionType::BZERO
:
160 return {"void", "(char * dst, size_t size)",
161 "splat_set", "(dst, 0)",
162 "(dst, 0, size)", ""};
164 report_fatal_error("Not yet implemented");
168 static StringRef
getAligntoString(const AlignArg
&AlignTo
) {
174 case AlignArg::ARRAY_SIZE
:
175 report_fatal_error("logic error");
179 static raw_ostream
&operator<<(raw_ostream
&Stream
, const ElementType
&E
) {
180 return Stream
<< '_' << E
.Size
;
182 static raw_ostream
&operator<<(raw_ostream
&Stream
, const Individual
&O
) {
183 return Stream
<< O
.Element
;
185 static raw_ostream
&operator<<(raw_ostream
&Stream
, const Overlap
&O
) {
186 return Stream
<< "HeadTail<" << O
.Element
<< '>';
188 static raw_ostream
&operator<<(raw_ostream
&Stream
, const Loop
&O
) {
189 return Stream
<< "Loop<" << O
.Element
<< '>';
191 static raw_ostream
&operator<<(raw_ostream
&Stream
, const AlignedLoop
&O
) {
192 return Stream
<< "Align<" << O
.Alignment
<< ',' << O
.AlignTo
<< ">::Then<"
193 << Loop
{O
.IfLt
, O
.Element
} << ">";
195 static raw_ostream
&operator<<(raw_ostream
&Stream
, const Accelerator
&O
) {
196 return Stream
<< "Accelerator";
199 template <typename T
> struct IfEq
{
205 template <typename T
> struct IfLt
{
211 static raw_ostream
&operator<<(raw_ostream
&Stream
, const Zero
&O
) {
212 Stream
<< kIndent
<< "if(size == 0) return";
213 if (!O
.DefaultReturnValue
.empty())
214 Stream
<< ' ' << O
.DefaultReturnValue
;
215 return Stream
<< ";\n";
218 template <typename T
>
219 static raw_ostream
&operator<<(raw_ostream
&Stream
, const IfEq
<T
> &O
) {
220 return Stream
<< kIndent
<< "if(size == " << O
.Element
.IfEq
<< ") return "
221 << O
.Op
<< '<' << O
.Element
<< '>' << O
.Args
<< ";\n";
224 template <typename T
>
225 static raw_ostream
&operator<<(raw_ostream
&Stream
, const IfLt
<T
> &O
) {
227 if (O
.Element
.IfLt
!= kMaxSize
)
228 Stream
<< "if(size < " << O
.Element
.IfLt
<< ") ";
229 return Stream
<< "return " << O
.Op
<< '<' << O
.Element
<< '>' << O
.Args
233 static raw_ostream
&operator<<(raw_ostream
&Stream
,
234 const ElementTypeClass
&Class
) {
236 case ElementTypeClass::SCALAR
:
237 return Stream
<< "scalar";
238 case ElementTypeClass::BUILTIN
:
239 return Stream
<< "builtin";
240 case ElementTypeClass::NATIVE
:
241 // FIXME: the framework should provide a `native` namespace that redirect to
242 // x86, arm or other architectures.
243 return Stream
<< "x86";
247 static raw_ostream
&operator<<(raw_ostream
&Stream
,
248 const FunctionImplementation
&FI
) {
249 const auto &Ctx
= FI
.Ctx
;
250 Stream
<< "static " << Ctx
.FunctionReturnType
<< ' ' << FI
.Name
251 << Ctx
.FunctionArgs
<< " {\n";
252 Stream
<< kIndent
<< "using namespace __llvm_libc::" << FI
.ElementClass
254 for (const auto &I
: FI
.Individuals
)
255 if (I
.Element
.Size
== 0)
256 Stream
<< Zero
{Ctx
.DefaultReturnValue
};
258 Stream
<< IfEq
<Individual
>{Ctx
.ElementOp
, Ctx
.FixedSizeArgs
, I
};
259 for (const auto &O
: FI
.Overlaps
)
260 Stream
<< IfLt
<Overlap
>{Ctx
.ElementOp
, Ctx
.RuntimeSizeArgs
, O
};
261 if (const auto &C
= FI
.Loop
)
262 Stream
<< IfLt
<Loop
>{Ctx
.ElementOp
, Ctx
.RuntimeSizeArgs
, *C
};
263 if (const auto &C
= FI
.AlignedLoop
)
264 Stream
<< IfLt
<AlignedLoop
>{Ctx
.ElementOp
, Ctx
.RuntimeSizeArgs
, *C
};
265 if (const auto &C
= FI
.Accelerator
)
266 Stream
<< IfLt
<Accelerator
>{Ctx
.ElementOp
, Ctx
.RuntimeSizeArgs
, *C
};
267 return Stream
<< "}\n";
270 // Turns a `NamedFunctionDescriptor` into a `FunctionImplementation` unfolding
271 // the contiguous and overlap region into several statements. The zero case is
272 // also mapped to its own type.
273 static FunctionImplementation
274 getImplementation(const NamedFunctionDescriptor
&NamedFD
) {
275 const FunctionDescriptor
&FD
= NamedFD
.Desc
;
276 FunctionImplementation Impl
;
277 Impl
.Ctx
= getCtx(FD
.Type
);
278 Impl
.Name
= NamedFD
.Name
;
279 Impl
.ElementClass
= FD
.ElementClass
;
280 if (auto C
= FD
.Contiguous
)
281 for (size_t I
= C
->Span
.Begin
; I
< C
->Span
.End
; ++I
)
282 Impl
.Individuals
.push_back(Individual
{I
, ElementType
{I
}});
283 if (auto C
= FD
.Overlap
)
284 for (size_t I
= C
->Span
.Begin
; I
< C
->Span
.End
; I
*= 2)
285 Impl
.Overlaps
.push_back(Overlap
{2 * I
, ElementType
{I
}});
286 if (const auto &L
= FD
.Loop
)
287 Impl
.Loop
= Loop
{L
->Span
.End
, ElementType
{L
->BlockSize
}};
288 if (const auto &AL
= FD
.AlignedLoop
)
290 AlignedLoop
{AL
->Loop
.Span
.End
, ElementType
{AL
->Loop
.BlockSize
},
291 ElementType
{AL
->Alignment
}, getAligntoString(AL
->AlignTo
)};
292 if (const auto &A
= FD
.Accelerator
)
293 Impl
.Accelerator
= Accelerator
{A
->Span
.End
};
297 static void Serialize(raw_ostream
&Stream
,
298 ArrayRef
<NamedFunctionDescriptor
> Descriptors
) {
300 for (const auto &FD
: Descriptors
)
301 Stream
<< getImplementation(FD
);
304 } // namespace functions
306 namespace descriptors
{
308 // This namespace generates the getFunctionDescriptors function:
309 // -------------------------------------------------------------
311 // ArrayRef<NamedFunctionDescriptor> getFunctionDescriptors() {
312 // static constexpr NamedFunctionDescriptor kDescriptors[] = {
313 // {"memcpy_0xE00E29EE73994E2B",{FunctionType::MEMCPY,std::nullopt,std::nullopt,std::nullopt,std::nullopt,Accelerator{{0,kMaxSize}},ElementTypeClass::NATIVE}},
314 // {"memcpy_0x8661D80472487AB5",{FunctionType::MEMCPY,Contiguous{{0,1}},std::nullopt,std::nullopt,std::nullopt,Accelerator{{1,kMaxSize}},ElementTypeClass::NATIVE}},
317 // return ArrayRef(kDescriptors);
320 static raw_ostream
&operator<<(raw_ostream
&Stream
, const SizeSpan
&SS
) {
321 Stream
<< "{" << SS
.Begin
<< ',';
322 if (SS
.End
== kMaxSize
)
323 Stream
<< "kMaxSize";
326 return Stream
<< '}';
328 static raw_ostream
&operator<<(raw_ostream
&Stream
, const Contiguous
&O
) {
329 return Stream
<< "Contiguous{" << O
.Span
<< '}';
331 static raw_ostream
&operator<<(raw_ostream
&Stream
, const Overlap
&O
) {
332 return Stream
<< "Overlap{" << O
.Span
<< '}';
334 static raw_ostream
&operator<<(raw_ostream
&Stream
, const Loop
&O
) {
335 return Stream
<< "Loop{" << O
.Span
<< ',' << O
.BlockSize
<< '}';
337 static raw_ostream
&operator<<(raw_ostream
&Stream
, const AlignArg
&O
) {
340 return Stream
<< "AlignArg::_1";
342 return Stream
<< "AlignArg::_2";
343 case AlignArg::ARRAY_SIZE
:
344 report_fatal_error("logic error");
347 static raw_ostream
&operator<<(raw_ostream
&Stream
, const AlignedLoop
&O
) {
348 return Stream
<< "AlignedLoop{" << O
.Loop
<< ',' << O
.Alignment
<< ','
351 static raw_ostream
&operator<<(raw_ostream
&Stream
, const Accelerator
&O
) {
352 return Stream
<< "Accelerator{" << O
.Span
<< '}';
354 static raw_ostream
&operator<<(raw_ostream
&Stream
, const ElementTypeClass
&O
) {
356 case ElementTypeClass::SCALAR
:
357 return Stream
<< "ElementTypeClass::SCALAR";
358 case ElementTypeClass::BUILTIN
:
359 return Stream
<< "ElementTypeClass::BUILTIN";
360 case ElementTypeClass::NATIVE
:
361 return Stream
<< "ElementTypeClass::NATIVE";
364 static raw_ostream
&operator<<(raw_ostream
&Stream
, const FunctionType
&T
) {
366 case FunctionType::MEMCPY
:
367 return Stream
<< "FunctionType::MEMCPY";
368 case FunctionType::MEMCMP
:
369 return Stream
<< "FunctionType::MEMCMP";
370 case FunctionType::BCMP
:
371 return Stream
<< "FunctionType::BCMP";
372 case FunctionType::MEMSET
:
373 return Stream
<< "FunctionType::MEMSET";
374 case FunctionType::BZERO
:
375 return Stream
<< "FunctionType::BZERO";
378 template <typename T
>
379 static raw_ostream
&operator<<(raw_ostream
&Stream
,
380 const std::optional
<T
> &MaybeT
) {
382 return Stream
<< *MaybeT
;
383 return Stream
<< "std::nullopt";
385 static raw_ostream
&operator<<(raw_ostream
&Stream
,
386 const FunctionDescriptor
&FD
) {
387 return Stream
<< '{' << FD
.Type
<< ',' << FD
.Contiguous
<< ',' << FD
.Overlap
388 << ',' << FD
.Loop
<< ',' << FD
.AlignedLoop
<< ','
389 << FD
.Accelerator
<< ',' << FD
.ElementClass
<< '}';
391 static raw_ostream
&operator<<(raw_ostream
&Stream
,
392 const NamedFunctionDescriptor
&NFD
) {
393 return Stream
<< '{' << '"' << NFD
.Name
<< '"' << ',' << NFD
.Desc
<< '}';
395 template <typename T
>
396 static raw_ostream
&operator<<(raw_ostream
&Stream
,
397 const std::vector
<T
> &VectorT
) {
400 for (const auto &Obj
: VectorT
) {
406 return Stream
<< '}';
409 static void Serialize(raw_ostream
&Stream
,
410 ArrayRef
<NamedFunctionDescriptor
> Descriptors
) {
411 Stream
<< R
"(ArrayRef<NamedFunctionDescriptor> getFunctionDescriptors() {
412 static constexpr NamedFunctionDescriptor kDescriptors[] = {
414 for (size_t I
= 0, E
= Descriptors
.size(); I
< E
; ++I
) {
415 Stream
<< kIndent
<< kIndent
<< Descriptors
[I
] << ",\n";
418 return ArrayRef(kDescriptors);
423 } // namespace descriptors
425 namespace configurations
{
427 // This namespace generates the getXXXConfigurations functions:
428 // ------------------------------------------------------------
430 // llvm::ArrayRef<MemcpyConfiguration> getMemcpyConfigurations() {
431 // using namespace __llvm_libc;
432 // static constexpr MemcpyConfiguration kConfigurations[] = {
433 // {Wrap<memcpy_0xE00E29EE73994E2B>, "memcpy_0xE00E29EE73994E2B"},
434 // {Wrap<memcpy_0x8661D80472487AB5>, "memcpy_0x8661D80472487AB5"},
437 // return llvm::ArrayRef(kConfigurations);
440 // The `Wrap` template function is provided in the `Main` function below.
441 // It is used to adapt the gnerated code to the prototype of the C function.
442 // For instance, the generated code for a `memcpy` takes `char*` pointers and
443 // returns nothing but the original C `memcpy` function take and returns `void*`
446 struct FunctionName
{
447 FunctionType ForType
;
451 FunctionType ForType
;
454 struct Configuration
{
457 std::vector
<const NamedFunctionDescriptor
*> Descriptors
;
460 static raw_ostream
&operator<<(raw_ostream
&Stream
, const FunctionName
&FN
) {
461 switch (FN
.ForType
) {
462 case FunctionType::MEMCPY
:
463 return Stream
<< "getMemcpyConfigurations";
464 case FunctionType::MEMCMP
:
465 return Stream
<< "getMemcmpConfigurations";
466 case FunctionType::BCMP
:
467 return Stream
<< "getBcmpConfigurations";
468 case FunctionType::MEMSET
:
469 return Stream
<< "getMemsetConfigurations";
470 case FunctionType::BZERO
:
471 return Stream
<< "getBzeroConfigurations";
475 static raw_ostream
&operator<<(raw_ostream
&Stream
, const ReturnType
&RT
) {
476 switch (RT
.ForType
) {
477 case FunctionType::MEMCPY
:
478 return Stream
<< "MemcpyConfiguration";
479 case FunctionType::MEMCMP
:
480 case FunctionType::BCMP
:
481 return Stream
<< "MemcmpOrBcmpConfiguration";
482 case FunctionType::MEMSET
:
483 return Stream
<< "MemsetConfiguration";
484 case FunctionType::BZERO
:
485 return Stream
<< "BzeroConfiguration";
489 static raw_ostream
&operator<<(raw_ostream
&Stream
,
490 const NamedFunctionDescriptor
*FD
) {
491 return Stream
<< formatv("{Wrap<{0}>, \"{0}\"}", FD
->Name
);
495 operator<<(raw_ostream
&Stream
,
496 const std::vector
<const NamedFunctionDescriptor
*> &Descriptors
) {
497 for (size_t I
= 0, E
= Descriptors
.size(); I
< E
; ++I
)
498 Stream
<< kIndent
<< kIndent
<< Descriptors
[I
] << ",\n";
502 static raw_ostream
&operator<<(raw_ostream
&Stream
, const Configuration
&C
) {
503 Stream
<< "llvm::ArrayRef<" << C
.Type
<< "> " << C
.Name
<< "() {\n";
504 if (C
.Descriptors
.empty())
505 Stream
<< kIndent
<< "return {};\n";
507 Stream
<< kIndent
<< "using namespace __llvm_libc;\n";
508 Stream
<< kIndent
<< "static constexpr " << C
.Type
509 << " kConfigurations[] = {\n";
510 Stream
<< C
.Descriptors
;
511 Stream
<< kIndent
<< "};\n";
512 Stream
<< kIndent
<< "return llvm::ArrayRef(kConfigurations);\n";
518 static void Serialize(raw_ostream
&Stream
, FunctionType FT
,
519 ArrayRef
<NamedFunctionDescriptor
> Descriptors
) {
523 for (const auto &FD
: Descriptors
)
524 if (FD
.Desc
.Type
== FT
)
525 Conf
.Descriptors
.push_back(&FD
);
529 } // namespace configurations
530 static void Serialize(raw_ostream
&Stream
,
531 ArrayRef
<NamedFunctionDescriptor
> Descriptors
) {
532 Stream
<< "// This file is auto-generated by libc/benchmarks/automemcpy.\n";
533 Stream
<< "// Functions : " << Descriptors
.size() << "\n";
535 Stream
<< "#include \"LibcFunctionPrototypes.h\"\n";
536 Stream
<< "#include \"automemcpy/FunctionDescriptor.h\"\n";
537 Stream
<< "#include \"src/string/memory_utils/elements.h\"\n";
539 Stream
<< "using llvm::libc_benchmarks::BzeroConfiguration;\n";
540 Stream
<< "using llvm::libc_benchmarks::MemcmpOrBcmpConfiguration;\n";
541 Stream
<< "using llvm::libc_benchmarks::MemcpyConfiguration;\n";
542 Stream
<< "using llvm::libc_benchmarks::MemmoveConfiguration;\n";
543 Stream
<< "using llvm::libc_benchmarks::MemsetConfiguration;\n";
545 Stream
<< "namespace __llvm_libc {\n";
547 codegen::functions::Serialize(Stream
, Descriptors
);
549 Stream
<< "} // namespace __llvm_libc\n";
551 Stream
<< "namespace llvm {\n";
552 Stream
<< "namespace automemcpy {\n";
554 codegen::descriptors::Serialize(Stream
, Descriptors
);
556 Stream
<< "} // namespace automemcpy\n";
557 Stream
<< "} // namespace llvm\n";
560 using MemcpyStub = void (*)(char *__restrict, const char *__restrict, size_t);
561 template <MemcpyStub Foo>
562 void *Wrap(void *__restrict dst, const void *__restrict src, size_t size) {
563 Foo(reinterpret_cast<char *__restrict>(dst),
564 reinterpret_cast<const char *__restrict>(src), size);
568 codegen::configurations::Serialize(Stream
, FunctionType::MEMCPY
, Descriptors
);
570 using MemcmpStub = int (*)(const char *, const char *, size_t);
571 template <MemcmpStub Foo>
572 int Wrap(const void *lhs, const void *rhs, size_t size) {
573 return Foo(reinterpret_cast<const char *>(lhs),
574 reinterpret_cast<const char *>(rhs), size);
577 codegen::configurations::Serialize(Stream
, FunctionType::MEMCMP
, Descriptors
);
578 codegen::configurations::Serialize(Stream
, FunctionType::BCMP
, Descriptors
);
580 using MemsetStub = void (*)(char *, int, size_t);
581 template <MemsetStub Foo> void *Wrap(void *dst, int value, size_t size) {
582 Foo(reinterpret_cast<char *>(dst), value, size);
586 codegen::configurations::Serialize(Stream
, FunctionType::MEMSET
, Descriptors
);
588 using BzeroStub = void (*)(char *, size_t);
589 template <BzeroStub Foo> void Wrap(void *dst, size_t size) {
590 Foo(reinterpret_cast<char *>(dst), size);
593 codegen::configurations::Serialize(Stream
, FunctionType::BZERO
, Descriptors
);
595 llvm::ArrayRef<MemmoveConfiguration> getMemmoveConfigurations() {
599 Stream
<< "// Functions : " << Descriptors
.size() << "\n";
602 } // namespace codegen
604 // Stores `VolatileStr` into a cache and returns a StringRef of the cached
606 StringRef
getInternalizedString(std::string VolatileStr
) {
607 static llvm::StringSet StringCache
;
608 return StringCache
.insert(std::move(VolatileStr
)).first
->getKey();
611 static StringRef
getString(FunctionType FT
) {
613 case FunctionType::MEMCPY
:
615 case FunctionType::MEMCMP
:
617 case FunctionType::BCMP
:
619 case FunctionType::MEMSET
:
621 case FunctionType::BZERO
:
626 void Serialize(raw_ostream
&Stream
, ArrayRef
<FunctionDescriptor
> Descriptors
) {
627 std::vector
<NamedFunctionDescriptor
> FunctionDescriptors
;
628 FunctionDescriptors
.reserve(Descriptors
.size());
629 for (auto &FD
: Descriptors
) {
630 FunctionDescriptors
.emplace_back();
631 FunctionDescriptors
.back().Name
= getInternalizedString(
632 formatv("{0}_{1:X16}", getString(FD
.Type
), FD
.id()));
633 FunctionDescriptors
.back().Desc
= std::move(FD
);
635 // Sort functions so they are easier to spot in the generated C++ file.
636 std::sort(FunctionDescriptors
.begin(), FunctionDescriptors
.end(),
637 [](const NamedFunctionDescriptor
&A
,
638 const NamedFunctionDescriptor
&B
) { return A
.Desc
< B
.Desc
; });
639 codegen::Serialize(Stream
, FunctionDescriptors
);
642 } // namespace automemcpy