From 81c38ef4ce0517aa355e2c1ca74097be39ecaec1 Mon Sep 17 00:00:00 2001 From: Tobias Grosser Date: Sat, 8 Apr 2017 05:12:50 +0200 Subject: [PATCH] cpp: generate C++ wrapper classes The generator creates for each exported isl object a smart pointer class of the following form: class set; inline isl::set manage(__isl_take isl_set *set); class set { friend inline isl::set manage(__isl_take isl_set *set); isl_set *ptr = nullptr; inline explicit set(__isl_take isl_set *ptr); public: inline set(); inline set(const isl::set &obj); inline set& operator=(isl::set obj); inline ~set(); inline __isl_give isl_set *copy() const &; inline __isl_give isl_set *copy() && = delete; inline __isl_keep isl_set *get() const; inline __isl_give isl_set *release(); inline bool is_null() const; }; The interface implements a smart pointer that applies value semantics to its pointee and is inspired by the value_ptr proposed in http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3339.pdf. The isl smart pointer keeps unique ownership of an object. The currently held pointer can be observed by calling get(). When calling release(), both the current pointer is returned and ownership is released. Names and semantics of get() and release() match the corresponding methods in value_ptr. In addition, a copy() method is provided, which returns a pointer to a copy of the managed object. These three methods allow the isl C++ objects to be used together with the isl C functions. On top of this an is_null method is provided to check if the object is currently not holding any reference. Example: void foo(Set S) { isl_set_is_universe(S.get()); isl_set_free(S.copy()); isl_set_free(S.release()); } The r-value version of the copy() function is not needed as copying an object that will be freed right after is inefficient. In such a situation, the use of release() is more efficient as it avoids the unnecessary object copy. By deleting the r-value version of copy this change ensures that the user does not accidentally use the copy() in situations where it is preferable to call release(). An alternative choice to name get() and release() would have been keep() and take(). The name 'take' is for example used in llvm::OwningPtr. For consistency with the value_ptr proposal and other smart pointers such as std::unique_ptr, this alternative naming has not been chosen. isl C++ objects can be constructed either by copying existing isl objects of the same type or by passing an isl C pointer to a global isl::manage(isl_type *ptr) method. Constructing an isl C++ object from an isl C pointer using an explicit class constructor is not allowed. This interface has been chosen in correspondence with std::make_unique. Besides making object creation and especially pointer consumption explicit, global methods also make specifying the needed object type unnecessary. This change also allows default construction of null isl objects, as default-constructability is important when using isl objects in C++ containers and especially when exploiting C++ move semantics. Null isl objects, are not backed up by any isl object, but correspond to a nullptr in the C interface. To complete the "Big Three", this commit also defines the copy assignment operator. The remaining two functions needed to complete the "Big Five", move constructor and move assignment, are not needed for an initial implementation and are left for future work. Example: void foo (__isl_take *s1, __isl_take *s2) { isl::set S1 = isl::manage(s1); auto S = isl::manage(s2); auto S2 = S1; isl::set ZeroSet; } The currently generated library is header-only, which resembles the already existing python bindings and is the best choice for a lightweight interface library. In the future, implementations for larger functions or debugging methods might require the addition of implementations as part of a compiled (not-header-only) library. The current library is generated in a single unified header file, which matches the generated python bindings. In the future, this might evolve to a combination of per-class header files and a unifying header file, as there is interest in both. However, for an initial binding a unified header has been chosen. No class hierarchy is constructed, as there is currently no obvious benefit and a hierarchy can always be introduced later. However, most conversions are probably better implemented through implicit constructors from related isl objects. Such constructors are added in the next commit. All functions are declared 'inline' to not cause link errors when included in multiple translation units. This change does not change error handling in case of nullptrs, but passes nullptrs directly to isl, relying on isl's ability to transparently forward them. Several people pointed out that C++ error handling mechanisms such as exceptions would allow for more C++ style error handling. However, without having actual users of exceptions, it was agreed to not support error notifications through exceptions. In case future use cases motivate exceptions, the isl C++ error handling approach can be improved. To clarify that this is a 'no-exception' namespace an inline namespace isl::noexceptions is used. C++ inline namespaces allow to introduce additional name spaces while at the same time making the definitions in these new namespaces available in the outer namespaces. This allows for future additions of isl bindings with exceptions without requiring the user to type additional namespace qualifiers. This change intentionally only introduces a minimalistic interface. The interface established with this commit is already sufficient to manage isl pointers. Polly today uses an isl C++ interface that has about the same functionality as this commit and already the simplified memory management has proven to facilitate software development a lot. The infrastructure established with this commit will serve as basis for a future function call interface that makes isl C functions conveniently accessible through the C++ interface. File IO is performed through ostream, as this makes it easy to split the bindings into multiple files later on. However, this commit still uses C style string formatting as this is more readable and closer to the python binding generator. This approach is similar to the string generation in Armin Groesslinger's SCALA bindings. These bindings use lower_case::method_and_class_names to match the style of the existing python bindings as well as the style of the isl code base. An alternative would have been to use CamelCase, which is recommended in many modern C++ style guides (e.g., the Google style guide) and would match well with the styles used in LLVM and Polly, but no agreement for CamelCase could be found. Hence, this commit stays with lower_case. The design of the proposed isl C++ smart pointer interface is the result of discussions with Michael Kruse, Jakob Leben, and Alexandre Isoard. It was inspired by earlier bindings developed by Michael, Jakob, and Alexandre, but also the C++ bindings developed by Andreas Simbuerger, the SCALA bindings from Armin Groesslinger, as well as islpy developed by Andreas Kloeckner. The actual code generator has been newly developed, but relies on three enabling patches from Armin Groesslinger, which establish an infrastructure also used in Armin's and Andreas' bindings. As a result, this commit also will make it easier to upstream Armin's SCALA bindings and the similar structure of this new code generator should make it easy to port changes from Andreas' C++ generator. Signed-off-by: Tobias Grosser Signed-off-by: Sven Verdoolaege --- interface/Makefile.am | 12 +- interface/cpp.cc | 453 +++++++++++++++++++++++++++++++++++++++++ interface/cpp.h | 38 ++++ interface/extract_interface.cc | 4 + interface/isl.h.top | 21 ++ 5 files changed, 526 insertions(+), 2 deletions(-) create mode 100644 interface/cpp.cc create mode 100644 interface/cpp.h create mode 100644 interface/isl.h.top diff --git a/interface/Makefile.am b/interface/Makefile.am index 4d2b7332..7a70990f 100644 --- a/interface/Makefile.am +++ b/interface/Makefile.am @@ -14,6 +14,8 @@ extract_interface_SOURCES = \ generator.cc \ python.h \ python.cc \ + cpp.h \ + cpp.cc \ extract_interface.h \ extract_interface.cc extract_interface_LDADD = \ @@ -33,5 +35,11 @@ isl.py: extract_interface isl.py.top $(srcdir)/all.h) \ > isl.py -dist-hook: isl.py - cp isl.py $(distdir)/ +isl-noexceptions.h: extract_interface all.h isl.h.top + (cat $(srcdir)/isl.h.top; \ + ./extract_interface$(EXEEXT) --language=cpp $(includes) \ + $(srcdir)/all.h) \ + > isl-noexceptions.h + +dist-hook: isl.py isl-noexceptions.h + cp isl.py isl-noexceptions.h $(distdir)/ diff --git a/interface/cpp.cc b/interface/cpp.cc new file mode 100644 index 00000000..ea0f7f9d --- /dev/null +++ b/interface/cpp.cc @@ -0,0 +1,453 @@ +/* + * Copyright 2016, 2017 Tobias Grosser. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY TOBIAS GROSSER ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SVEN VERDOOLAEGE OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation + * are those of the authors and should not be interpreted as + * representing official policies, either expressed or implied, of + * Tobias Grosser. + */ + +#include +#include +#include +#include +#include + +#include "cpp.h" +#include "isl_config.h" + +/* Print string formatted according to "fmt" to ostream "os". + * + * This osprintf method allows us to use printf style formatting constructs when + * writing to an ostream. + */ +static void osprintf(ostream &os, const char *format, ...) +{ + va_list arguments; + FILE *string_stream; + char *string_pointer; + size_t size; + + va_start(arguments, format); + + string_stream = open_memstream(&string_pointer, &size); + + if (!string_stream) { + fprintf(stderr, "open_memstream failed -- aborting!\n"); + exit(1); + } + + vfprintf(string_stream, format, arguments); + fclose(string_stream); + os << string_pointer; + free(string_pointer); +} + +/* Generate a cpp interface based on the extracted types and functions. + * + * Print first a set of forward declarations for all isl wrapper + * classes, then the declarations of the classes, and at the end all + * implementations. + */ +void cpp_generator::generate() +{ + ostream &os = cout; + + osprintf(os, "\n"); + osprintf(os, "namespace isl {\n\n"); + osprintf(os, "inline namespace noexceptions {\n\n"); + + print_forward_declarations(os); + osprintf(os, "\n"); + print_declarations(os); + osprintf(os, "\n"); + print_implementations(os); + + osprintf(os, "} // namespace noexceptions\n"); + osprintf(os, "} // namespace isl\n\n"); + osprintf(os, "#endif /* ISL_CPP_NOEXCEPTIONS */\n"); +} + +/* Print forward declarations for all classes to "os". +*/ +void cpp_generator::print_forward_declarations(ostream &os) +{ + map::iterator ci; + + osprintf(os, "// forward declarations\n"); + + for (ci = classes.begin(); ci != classes.end(); ++ci) + print_class_forward_decl(os, ci->second); +} + +/* Print all declarations to "os". + */ +void cpp_generator::print_declarations(ostream &os) +{ + map::iterator ci; + bool first = true; + + for (ci = classes.begin(); ci != classes.end(); ++ci) { + if (first) + first = false; + else + osprintf(os, "\n"); + + print_class(os, ci->second); + } +} + +/* Print all implementations to "os". + */ +void cpp_generator::print_implementations(ostream &os) +{ + map::iterator ci; + bool first = true; + + for (ci = classes.begin(); ci != classes.end(); ++ci) { + if (first) + first = false; + else + osprintf(os, "\n"); + + print_class_impl(os, ci->second); + } +} + +/* Print declarations for class "clazz" to "os". + */ +void cpp_generator::print_class(ostream &os, const isl_class &clazz) +{ + const char *name = clazz.name.c_str(); + std::string cppstring = type2cpp(clazz); + const char *cppname = cppstring.c_str(); + + osprintf(os, "// declarations for isl::%s\n", cppname); + + print_class_factory_decl(os, clazz); + osprintf(os, "\n"); + osprintf(os, "class %s {\n", cppname); + osprintf(os, " friend "); + print_class_factory_decl(os, clazz); + osprintf(os, "\n"); + osprintf(os, " %s *ptr = nullptr;\n", name); + osprintf(os, "\n"); + print_private_constructors_decl(os, clazz); + osprintf(os, "\n"); + osprintf(os, "public:\n"); + print_public_constructors_decl(os, clazz); + print_copy_assignment_decl(os, clazz); + print_destructor_decl(os, clazz); + print_ptr_decl(os, clazz); + + osprintf(os, "};\n"); +} + +/* Print forward declaration of class "clazz" to "os". + */ +void cpp_generator::print_class_forward_decl(ostream &os, + const isl_class &clazz) +{ + std::string cppstring = type2cpp(clazz); + const char *cppname = cppstring.c_str(); + + osprintf(os, "class %s;\n", cppname); +} + +/* Print global factory function to "os". + * + * Each class has one global factory function: + * + * isl::set manage(__isl_take isl_set *ptr); + * + * The only public way to construct isl C++ objects from a raw pointer is + * through this global factory function. This ensures isl object creation + * is very explicit and pointers are not converted by accident. Due to + * overloading, manage() can be called on any isl raw pointer and the + * corresponding object is automatically created, without the user having + * to choose the right isl object type. + */ +void cpp_generator::print_class_factory_decl(ostream &os, + const isl_class &clazz) +{ + const char *name = clazz.name.c_str(); + std::string cppstring = type2cpp(clazz); + const char *cppname = cppstring.c_str(); + + osprintf(os, "inline isl::%s manage(__isl_take %s *ptr);\n", cppname, + name); +} + +/* Print declarations of private constructors for class "clazz" to "os". + * + * Each class has currently one private constructor: + * + * 1) Constructor from a plain isl_* C pointer + * + * Example: + * + * set(__isl_take isl_set *ptr); + * + * The raw pointer constructor is kept private. Object creation is only + * possible through isl::manage(). + */ +void cpp_generator::print_private_constructors_decl(ostream &os, + const isl_class &clazz) +{ + const char *name = clazz.name.c_str(); + std::string cppstring = type2cpp(clazz); + const char *cppname = cppstring.c_str(); + + osprintf(os, " inline explicit %s(__isl_take %s *ptr);\n", cppname, + name); +} + +/* Print declarations of public constructors for class "clazz" to "os". + * + * Each class currently has two public constructors: + * + * 1) A default constructor + * 2) A copy constructor + * + * Example: + * + * set(); + * set(const isl::set &set); + */ +void cpp_generator::print_public_constructors_decl(ostream &os, + const isl_class &clazz) +{ + std::string cppstring = type2cpp(clazz); + const char *cppname = cppstring.c_str(); + osprintf(os, " inline /* implicit */ %s();\n", cppname); + + osprintf(os, " inline /* implicit */ %s(const isl::%s &obj);\n", + cppname, cppname); +} + +/* Print declarations of copy assignment operator for class "clazz" + * to "os". + * + * Each class has one assignment operator. + * + * isl:set &set::operator=(isl::set obj) + * + */ +void cpp_generator::print_copy_assignment_decl(ostream &os, + const isl_class &clazz) +{ + std::string cppstring = type2cpp(clazz); + const char *cppname = cppstring.c_str(); + + osprintf(os, " inline isl::%s &operator=(isl::%s obj);\n", cppname, + cppname); +} + +/* Print declaration of destructor for class "clazz" to "os". + */ +void cpp_generator::print_destructor_decl(ostream &os, const isl_class &clazz) +{ + std::string cppstring = type2cpp(clazz); + const char *cppname = cppstring.c_str(); + + osprintf(os, " inline ~%s();\n", cppname); +} + +/* Print declaration of pointer functions for class "clazz" to "os". + * + * To obtain a raw pointer three functions are provided: + * + * 1) __isl_give isl_set *copy() + * + * Returns a pointer to a _copy_ of the internal object + * + * 2) __isl_keep isl_set *get() + * + * Returns a pointer to the internal object + * + * 3) __isl_give isl_set *release() + * + * Returns a pointer to the internal object and resets the + * internal pointer to nullptr. + * + * We also provide functionality to explicitly check if a pointer is + * currently managed by this object. + * + * 4) bool is_null() + * + * Check if the current object is a null pointer. + * + * The functions get() and release() model the value_ptr proposed in + * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3339.pdf. + * The copy() function is an extension to allow the user to explicitly + * copy the underlying object. + * + * Also generate a declaration to delete copy() for r-values, for + * r-values release() should be used to avoid unnecessary copies. + */ +void cpp_generator::print_ptr_decl(ostream &os, const isl_class &clazz) +{ + const char *name = clazz.name.c_str(); + + osprintf(os, " inline __isl_give %s *copy() const &;\n", name); + osprintf(os, " inline __isl_give %s *copy() && = delete;\n", name); + osprintf(os, " inline __isl_keep %s *get() const;\n", name); + osprintf(os, " inline __isl_give %s *release();\n", name); + osprintf(os, " inline bool is_null() const;\n"); +} + +/* Print implementations for class "clazz" to "os". + */ +void cpp_generator::print_class_impl(ostream &os, const isl_class &clazz) +{ + std::string cppstring = type2cpp(clazz); + const char *cppname = cppstring.c_str(); + + osprintf(os, "// implementations for isl::%s\n", cppname); + + print_class_factory_impl(os, clazz); + osprintf(os, "\n"); + print_public_constructors_impl(os, clazz); + osprintf(os, "\n"); + print_private_constructors_impl(os, clazz); + osprintf(os, "\n"); + print_copy_assignment_impl(os, clazz); + osprintf(os, "\n"); + print_destructor_impl(os, clazz); + osprintf(os, "\n"); + print_ptr_impl(os, clazz); +} + +/* Print implementation of global factory function to "os". + */ +void cpp_generator::print_class_factory_impl(ostream &os, + const isl_class &clazz) +{ + const char *name = clazz.name.c_str(); + std::string cppstring = type2cpp(clazz); + const char *cppname = cppstring.c_str(); + + osprintf(os, "isl::%s manage(__isl_take %s *ptr) {\n", cppname, name); + osprintf(os, " return %s(ptr);\n", cppname); + osprintf(os, "}\n"); +} + +/* Print implementations of private constructors for class "clazz" to "os". + */ +void cpp_generator::print_private_constructors_impl(ostream &os, + const isl_class &clazz) +{ + const char *name = clazz.name.c_str(); + std::string cppstring = type2cpp(clazz); + const char *cppname = cppstring.c_str(); + + osprintf(os, "%s::%s(__isl_take %s *ptr)\n : ptr(ptr) {}\n", + cppname, cppname, name); +} + +/* Print implementations of public constructors for class "clazz" to "os". + */ +void cpp_generator::print_public_constructors_impl(ostream &os, + const isl_class &clazz) +{ + const char *name = clazz.name.c_str(); + std::string cppstring = type2cpp(clazz); + const char *cppname = cppstring.c_str(); + + osprintf(os, "%s::%s()\n : ptr(nullptr) {}\n\n", cppname, cppname); + osprintf(os, "%s::%s(const isl::%s &obj)\n : ptr(obj.copy()) {}\n", + cppname, cppname, cppname, name); +} + +/* Print implementation of copy assignment operator for class "clazz" to "os". + */ +void cpp_generator::print_copy_assignment_impl(ostream &os, + const isl_class &clazz) +{ + const char *name = clazz.name.c_str(); + std::string cppstring = type2cpp(clazz); + const char *cppname = cppstring.c_str(); + + osprintf(os, "%s &%s::operator=(isl::%s obj) {\n", cppname, + cppname, cppname); + osprintf(os, " std::swap(this->ptr, obj.ptr);\n", name); + osprintf(os, " return *this;\n"); + osprintf(os, "}\n"); +} + +/* Print implementation of destructor for class "clazz" to "os". + */ +void cpp_generator::print_destructor_impl(ostream &os, + const isl_class &clazz) +{ + const char *name = clazz.name.c_str(); + std::string cppstring = type2cpp(clazz); + const char *cppname = cppstring.c_str(); + + osprintf(os, "%s::~%s() {\n", cppname, cppname); + osprintf(os, " if (ptr)\n"); + osprintf(os, " %s_free(ptr);\n", name); + osprintf(os, "}\n"); +} + +/* Print implementation of ptr() functions for class "clazz" to "os". + */ +void cpp_generator::print_ptr_impl(ostream &os, const isl_class &clazz) +{ + const char *name = clazz.name.c_str(); + std::string cppstring = type2cpp(clazz); + const char *cppname = cppstring.c_str(); + + osprintf(os, "__isl_give %s *%s::copy() const & {\n", name, cppname); + osprintf(os, " return %s_copy(ptr);\n", name); + osprintf(os, "}\n\n"); + osprintf(os, "__isl_keep %s *%s::get() const {\n", name, cppname); + osprintf(os, " return ptr;\n"); + osprintf(os, "}\n\n"); + osprintf(os, "__isl_give %s *%s::release() {\n", name, cppname); + osprintf(os, " %s *tmp = ptr;\n", name); + osprintf(os, " ptr = nullptr;\n"); + osprintf(os, " return tmp;\n"); + osprintf(os, "}\n\n"); + osprintf(os, "bool %s::is_null() const {\n", cppname); + osprintf(os, " return ptr == nullptr;\n"); + osprintf(os, "}\n"); +} + +/* Translate isl class "clazz" to its corresponding C++ type. + */ +string cpp_generator::type2cpp(const isl_class &clazz) +{ + return type2cpp(clazz.name); +} + +/* Translate type string "type_str" to its C++ name counterpart. +*/ +string cpp_generator::type2cpp(string type_str) +{ + return type_str.substr(4); +} diff --git a/interface/cpp.h b/interface/cpp.h new file mode 100644 index 00000000..a098a364 --- /dev/null +++ b/interface/cpp.h @@ -0,0 +1,38 @@ +#include "generator.h" + +using namespace std; +using namespace clang; + +class cpp_generator : public generator { +public: + cpp_generator(set &exported_types, + set functions) : + generator(exported_types, {}, functions) {} + + virtual void generate(); +private: + void print_forward_declarations(ostream &os); + void print_declarations(ostream &os); + void print_class(ostream &os, const isl_class &clazz); + void print_class_forward_decl(ostream &os, const isl_class &clazz); + void print_class_factory_decl(ostream &os, const isl_class &clazz); + void print_private_constructors_decl(ostream &os, + const isl_class &clazz); + void print_copy_assignment_decl(ostream &os, const isl_class &clazz); + void print_public_constructors_decl(ostream &os, + const isl_class &clazz); + void print_destructor_decl(ostream &os, const isl_class &clazz); + void print_ptr_decl(ostream &os, const isl_class &clazz); + void print_implementations(ostream &os); + void print_class_impl(ostream &os, const isl_class &clazz); + void print_class_factory_impl(ostream &os, const isl_class &clazz); + void print_private_constructors_impl(ostream &os, + const isl_class &clazz); + void print_public_constructors_impl(ostream &os, + const isl_class &clazz); + void print_copy_assignment_impl(ostream &os, const isl_class &clazz); + void print_destructor_impl(ostream &os, const isl_class &clazz); + void print_ptr_impl(ostream &os, const isl_class &clazz); + string type2cpp(const isl_class &clazz); + string type2cpp(string type_string); +}; diff --git a/interface/extract_interface.cc b/interface/extract_interface.cc index 11186887..64859053 100644 --- a/interface/extract_interface.cc +++ b/interface/extract_interface.cc @@ -76,6 +76,7 @@ #include "extract_interface.h" #include "generator.h" #include "python.h" +#include "cpp.h" using namespace std; using namespace clang; @@ -449,6 +450,9 @@ int main(int argc, char *argv[]) if (Language.compare("python") == 0) gen = new python_generator(consumer.exported_types, consumer.exported_functions, consumer.functions); + else if (Language.compare("cpp") == 0) + gen = new cpp_generator(consumer.exported_types, + consumer.functions); else cerr << "Language '" << Language << "' not recognized." << endl << "Not generating bindings." << endl; diff --git a/interface/isl.h.top b/interface/isl.h.top new file mode 100644 index 00000000..d4810d8f --- /dev/null +++ b/interface/isl.h.top @@ -0,0 +1,21 @@ +/// These are automatically generated C++ bindings for isl. +/// +/// isl is a library for computing with integer sets and maps described by +/// Presburger formulas. On top of this, isl provides various tools for +/// polyhedral compilation, ranging from dependence analysis over scheduling +/// to AST generation. + +#ifndef ISL_CPP_NOEXCEPTIONS +#define ISL_CPP_NOEXCEPTIONS + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -- 2.11.4.GIT