.. _cpp-backend-label: More about C++ code generator ============================= Presentation & prerequisites ---------------------------- Presentation ^^^^^^^^^^^^ The C++ back-end produces a set of C++ source files along with a set of `CMake `_ scripts used to compile the generated C++ files and link them with an engine. Prerequisites ^^^^^^^^^^^^^ In order to use the code generated by the C++ back-end, you need to install the following dependencies: * `CMake `_, at least version 2.8.2. It may work with earlier versions, but it has not been tested. * `GNU Make `_. * A C++ compiler that supports the `STL `_. In addition, a support for `C++0x `_ is required when compiling with the optimized engine, and `C++11 `_ for the multithread engine. We are currently working with the GNU compiler g++ version 4.8.2, and for ABI compatibility issues we recommend to use g++ version 4.8 or higher. .. TIP:: On GNU/Debian or derivatives, use: ``$ apt-get install cmake make g++`` Usage ----- To generate C++ code, extra parameters must be used to drive C++ code generation. .. IMPORTANT:: If you are not using the standard compiler distribution, then you need to take care of the correct loading of the C++ back-end: its jar file must be in the classpath and the java property ``bip.compiler.backends`` must contain the string ``ujf.verimag.bip.backend.cpp.CppBackend`` The current C++ code generation requires the presence of an instance model, thus you must provide a *root* declaration (see ``-d`` in the above section). To enable the C++ back-end, you simply need to give an output directory: * ``--gencpp-output-dir`` followed by the directory that will contain all files generated by the C++ back-end. Example: :: $ bipc.sh -p SamplePackage -I /home/a_user/my_bip_lib/ -d "MyType()" \ --gencpp-output-dir /home/a_user/output/ The directory ``/home/a_user/output`` should contain several files & directories: :: . ÷── CMakeLists.txt ÷── Deploy ÷   ÷── Deploy.cpp |   ÷── Deploy.hpp │   `── DeployTypes.hpp ÷── SamplePackage │   ÷── CMakeLists.txt │   ÷── include │   │   `── SamplePackage │   │   ÷── CT_MyType.hpp │   │   ÷── AtomEPort_Port__t.hpp │   │   ÷── AtomIPort_Port__t.hpp ... │   `── src │   `── SamplePackage │   ÷── CT_MyType.cpp │   ÷── AtomEPort_Port__t.cpp │   ÷── AtomIPort_Port__t.cpp ... You don't need to dig into these directories, but it's always better to understand how the compiler organizes the generated files: * a *master* ``CMakeLists.txt`` that will be used to compile and link everything (generated code and engine code) together. Its use will be demonstrated later. * a directory ``SamplePackage`` containing : * a ``CMakeLists.txt`` with directives to compile the package * an ``include`` directory with all *header* files (*ie.* ``.hpp`` files). * a ``src`` directory with all implementation files (*ie.* ``.cpp`` files). * a directory ``Deploy`` with a 3 files with the directives for the concrete deployment of the running system. By default, the compiler won't resolve dependencies and will fail in case of inter-package reference. You need to provide ``--gencpp-follow-used-packages`` to resolve and compile dependencies. Interface BIP/C++ ----------------- Presentation ^^^^^^^^^^^^ It is very common to interface BIP code with external C++ code (*eg.* legacy code, specific code, ...). The current back-end provides you with several ways to interface your BIP code with external C++ code. Both *ways* of interfacing may need to add directory to the C++ compiler include search path. This can be achieved by using this command line argument: * ``--gencpp-cc-I`` : adds a directory to the compiler search paths for *include* files (*ie.* this is the ``-I`` used by most C++ compilers) At the package/type level """"""""""""""""""""""""" You can add one or more source file (*ie.* ``.cpp`` file) or object file (*ie.* ``.o`` file) *attached* to a package/a type. These source file will be compiled at the same time as the generated files corresponding to the package/type and the object files will be merged with the compiled code inside the library (*ie.* ``.a`` file) for the package. You can also add *include* directives that will be added to type/package generated files. You need to use annotations in the BIP source file (see :ref:`cpp-annotations-label`). At the global level """"""""""""""""""" You can inject source or object code at the global level or force the linking with an external library. Source code injected at this level will be compiled after all packages have been compiled. Object code or library are simply linked with all the other compiled code. To achieve this integration, you can use the following parameters: * ``--gencpp-cc-extra-src`` : adds a source file in the compilation process. * ``--gencpp-ld-L`` : adds a directory to the linker search paths for libraries (*ie.* this is the ``-L`` used by most linkers) * ``--gencpp-ld-l`` : adds a library to the link list (*ie.* this is the ``-l`` used by most linkers) * ``--gencpp-ld-extra-obj`` : adds an object file to the link list .. figure: ../images/missing-todo.png Compilation flow + where to inject Data handling ^^^^^^^^^^^^^ It is possible to use data when calling external C++ code. There are two important facts to keep in mind: * It is important to understand in which context the call is made as the function being called depends on that. * A function call is NEVER type-checked by the BIP compiler. It means that you can easily write WRONG code. Hopefully, your C++ compiler will catch bad cases (but don't rely on that). A function call can take data parameters and can return a single data value. For context where the callee can change the data (*ie.* connector ``down{}`` and petrinet transition ``do{}``): * function call ``f()`` in BIP is mapped to a C++ function call ``f()``. * the BIP assignment ``x = f()`` is mapped to the equivalent C++ ``corresponding_internal_data_var = f()`` . Type compatibility checked by C++ compiler. * function call with data argument ``f(a,b,c)`` with ``a``, ``b`` and ``c`` local BIP data declared in the caller's scope (atom, connector) is mapped in C++ to ``f(internal_data_a, internal_data_b, internal_data_c)``. Expected prototype for f: ``f(T1 &a, T2 &b, T3 &c)``. * the BIP assignment ``x = f(a,b,c)`` is mapped to C++ ``internal_data_x = f(internal_data_a, internal_data_b, internal_data_c)``, with expected prototype: ``T1 f(T2 &a, T3 &b, T4 &c)``. Beware that the return type is not a reference nor a pointer. If you need to avoid useless copy, you can have the output variable be a parameter and modify it from within the function body (*ie.* *by-reference* parameter). For context where the data used must *not* be modified (*ie.* const context: ``up{}`` and ``provided()``), all function call are prefixed by ``const_``: * function call ``f()`` is mapped in C++ to ``const_f()`` * ``x = f()`` is mapped to ``corresponding_internal_data_var = const_f()``. Type compatibility checked by C++ compiler. * ``f(a,b,c)`` with ``a``, ``b`` and ``c`` local data declared in the caller's scope (atom, connector) , mapped to ``const_f(internal_data_a, internal_data_b, internal_data_c)``. Expected prototype for f: ``f(const T1 &a, const T2 &b, const T3 &c)``. The const are only expected. If ``const_f()`` does not take const argument, it will still work, but system data may be altered by error. The const-ness is not a guaranty, it's only a good guide that avoids making mistakes. * ``x = f(a,b,c)`` mapped to ``internal_data_x = const_f(internal_data_a, internal_data_b, internal_data_c)``, with expected prototype: ``T1 f(const T2 &a, const T3 &b, const T4 &c)``. Beware that the return type is not a reference nor a pointer. This in order to avoid useless copy. .. HINT:: C++ code generator uses different function names instead of relying on C++ dispatching mechanism between *const* and *non-const* function because it doing so would imply that the compiler is able to *type* function parameters, which is currently not the case. .. IMPORTANT:: When using custom types, you may run into problems when using the reference engine as it tries to display a serialized version of the data during execution. This serialization relies on the C++ stream mechanism. If your data type does not support stream operation, the generated code won't compile. You can disable serialization when running the compiler with ``--gencpp-no-serial`` (no data will be displayed in execution traces). The :ref:`tutorial-cpp-label` has examples of BIP/C++ interfacing. Handling component parameter """""""""""""""""""""""""""" If you need to use a component parameter in an external function call, the parameter in the function prototype must *not* be a reference. Treat component parameters as direct value or expression:: atom type AT(int x) ... on p from S to T do {f(x);} ... end The function must look like:: void f(int x); If you try to use a reference, the C++ compiler will fail. Pass by reference/copy """""""""""""""""""""" When an external function takes a data variable (*ie.* atom data, component exported data, connector data) as parameter, do not forget to use a *reference* in the function prototype. Even if omitted, the code will still compile flawlessly, but the function will work on a *copy* of the data variable, not the variable itself. Any modification will be lost and strange behavior can arise because of the unwanted use of the copy constructor. If the function is given a data from a component type parameter or a direct value, then the corresponding function parameter must *not* be a reference. For example:: atom type AT() data int x ... on p from S to T do {f(x);} ... end ``f`` should have the following prototype:: void f(int &x); If you use :: void f(int x); The code will run, but all modifications of ``x`` within the ``f`` function will be lost when the function returns. It will also have an overhead as data will be copied at invocation. If the function takes a data from the type parameter, like the following:: atom type AT(int x1) data in x ... on p from S to T do {f(x, x1, 1+4);} ... end ``f`` should have the following prototype:: void f(int &a, int b, int c); Parameters ---------- * ``--gencpp-cc-I`` * ``--gencpp-cc-extra-src`` * ``--gencpp-ld-L`` * ``--gencpp-ld-l`` * ``--gencpp-ld-extra-obj`` * ``--gencpp-follow-used-packages`` * ``--gencpp-no-serial`` * ``--gencpp-disable-optim`` * ``--gencpp-enable-optim`` * ``--gencpp-optim`` * ``--gencpp-set-optim-param`` * ``--gencpp-enable-bip-debug`` .. _cpp-optimizations-label: Optimisation ------------ The C++ back-end can apply some optimization techniques. You can enable them either one by one, or by using predefined groups. To enable all optimizations up to level 2:: $ bipc.sh ... --gencpp-optim 2 To enable the use of a pool of interaction object of size 200:: $ bipc.sh ... --gencpp-enable-optim poolci \ --gencpp-set-optim-param poolci:size:2 Currently, the following optimizations are available: * ``rdvconnector`` (level : 1): generates specific code for *rendez-vous* connectors. * ``poolci`` (level :2) : dynamically created interaction object can be reused. When released, an interaction is placed in a *pool*. When a lot of interactions are involved, it lightens the burden on the memory allocator. The cost is that some memory is never released. * ``poolciv`` (level : 2): same as ``poolciv`` but for *interaction value* objects. * ``ports-reset`` (level: 2): allows to reduce recomputation of interactions and internal ports after components execution, based on static analysis of the code executed by transitions of atomic components. This optimization is only exploited by the optimized engine (i.e. no gain when using the reference engine). * ``no-side-effect`` (level: 3): improves other optimizations (currently concerns only optimization ``ports-reset``) by assuming that assignments of a variable ``v`` of an external type only modify ``v`` (e.g. no side effect on any other variable due to aliasing), and that calls to external functions can only modify the variables provided as parameters. Both ``poolci`` and ``poolciv`` accepts an optional parameter ``size`` to set the size of the pool. Beware that a pool of fixed size is created for every connector instance. .. _cpp-annotations-label: Debugging --------- BIP tools do not include a full featured debugger. Instead, we provide a mapping between the generated C++ code (on which any C++ debugger can be used) and the BIP source code. To enable this mechanism, you need to compile the code using ``--gencpp-enable-bip-debug``. The direct benefits are: * use of breakpoints in BIP source code * step by step execution in BIP source code The direct drawbacks are: * it is not possible to print data using BIP variable names, you need to dig into the generated code, which is less easy since it is the BIP code that gets displayed. * incoherences/unexpected debuger behavior can appear, as the mapping is not necessarily bijective (*eg.* a BIP guard could be duplicated in two locations in the generated code) .. IMPORTANT:: You need to compile the C++ with debugging support. Use the ``Debug`` profile included in the cmake scripts:: $ cmake -DCMAKE_BUILD_TYPE=Debug ..... Annotations ----------- ``@cpp(src="")`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^ * **scope** : package definition, any type definition * **argument** : comma separated list of file names * **role** : the files specified as argument will be inserted in the file list used during the compilation process along with files generated with the object to which the annotation is attached. .. TIP:: example: :: @cpp(src="something1.cpp,something2.cpp") atom type SomeAtom() ... end ``@cpp(obj="")`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^ * **scope** : package definition, any type definition * **argument** : comma separated list of file names * **role** : the files specified as argument will be inserted in the file list of objects to be linked with objects obtained by the compilation of the generated C++ files (obtained from the object to which the annotation is attached). .. IMPORTANT:: You will need to give the linker the paths containing your objects files using ``--gencpp-ld-L`` .. TIP:: example: :: @cpp(src="a/path/something1.o") atom type SomeAtom() ... end ``@cpp(include="")`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * **scope** : package definition, any type definition * **argument** : comma separated list of file names * **role** : each file in the list will trigger an *include* directive (*ie.* ``#include `` in the corresponding generated code. .. IMPORTANT:: The C++ compiler search path must be set accordingly using ``--gencpp-cc-I``. .. TIP:: example: :: @cpp(include="a/path/something1.hpp,stdio.h") atom type SomeAtom() ... end What you should never do ------------------------ In this section, we give examples of things you should *never* do. All these examples will compile and run, and sometimes have the behavior you expected. But they all break at least one the strong asumptions on which BIP is based. This means that even if *it looks ok at execution*, you will most probably get incorrect result with other tools (*eg.* model checking). Non-deterministic external code ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The most simple example of a *non-deterministic* code is the use of standard library's ``random()`` function. For example, consider the following package:: @cpp(include="stdio.h,stdlib.h") package bad port type Port_t() atom type BadAtom() data int d port Port_t p() place I,S1,S2 initial to I do { d = 0;} on p from I to S1 do { d = random()%5; } on p from S1 to S1 provided (d > 0) do { d = d - 1;} on p from S1 to S2 provided (d <= 0) end compound type Top() component BadAtom c() end end The following assumption: "From a given system state (here, atom ``c`` in state ``I`` and ``d`` equals ``0``), triggering a transition ``t`` always transforms the system state in the same state (here, atom ``c`` in state ``S1`` with ``d`` equals some value)" is broken. Even if there is only one single transition possible in the petrinet from state``I`` to ``S1``, the system state remains unknown as the value for ``d`` is not always the same. Even if this may be the expected behavior, this is a problem when verification tools are used. For example, the exploration heavily relies on the assumption being broken and thus, will produce incorrect results for this example. Side-effects in guards or ``up{}`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ As explained earlier, all guards and connector ``up{}`` must not have side effects on the system. This is very important, as the engine may execute several times these methods or it may cache their results: you can't predict how these will be executed. The BIP compiler prevents the user from writing wrong statements, but as always when using external code, it is still possible to make mistake. The following example illustrates both cases: * the ``guard()`` method, that should not modify its data parameter will in fact modify them by calling ``wrong_guard_ip()`` * the ``up{}`` will also call a function ``wrong_up()`` that will modify data bound to the connector's ports. Such an example demonstrates both a wrong execution and incorrect verification results:: @cpp(include="stdio.h,sideeffects.hpp") package sideeffects port type Port_t(int x) atom type Atom_t(int x) data int id, dat export port Port_t ep(dat) port Port_t ip(dat), ip2(dat) place I,IP,EP initial to I do {id = x; dat = 999;} on ip2 from I to I provided (wrong_guard_ip(dat) && 0 == 1) on ip from I to IP do { printf("id:%d, data:%d\n", id, dat); } on ep from I to EP end connector type LowC_t(Port_t p1, Port_t p2) data int d export port Port_t ep(d) define p1' p2' on p1 p2 up { d = 0; wrong_up(p1.x); wrong_up(p2.x); } on p2 up { d = 0; wrong_up(p2.x); } on p1 up { d = 0; wrong_up(p1.x); } end connector type HighC_t(Port_t p1, Port_t p2) define p1 p2 end compound type Top() component Atom_t c1(1), c2(2), c3(3) connector LowC_t lowc(c1.ep, c2.ep) connector HighC_t highc(lowc.ep, c3.ep) end end With ``sideeffects.hpp`` containing:: static void const_wrong_up(int &px){ px = -1; } static int const_wrong_guard_ip(int &d){ d = -1; return 0; } The associated execution trace illustrates clearly the problem regarding the ``wrong_guard_ip()``. Even though the transition labeled by ``ip2`` is never possible, its guard gets executed, and so, internal data is modified. When the transition labeled by ``ip`` is triggered, we can see that the data has been wrongly modified (no state change should have been made since the initialization of the system):: [BIP ENGINE]: initialize components... [BIP ENGINE]: state #0: 1 interaction and 3 internal ports: [BIP ENGINE]: [0] ROOT.highc: ROOT.lowc.ep({x}=0;) ROOT.c3.ep({x}=-1;) [BIP ENGINE]: [1] ROOT.c1._iport_decl__ip [BIP ENGINE]: [2] ROOT.c2._iport_decl__ip [BIP ENGINE]: [3] ROOT.c3._iport_decl__ip [BIP ENGINE]: -> choose [1] ROOT.c2._iport_decl__ip id:2, data:-1 The problem with the ``wrong_up()`` function is more subtle. The value changed is not the atom's data but a *port value*. This *port value* is used to compute interactions and evaluate guards of connectors. Modifying it will lead silently to an undefined state (*eg.* some interactions may be executed even though their guards should have prevented it). Troubleshooting --------------- The following is not an exhaustive list of errors with their explanations as most error messages should be self-explained. We give details about more obscur messages that usually deal with low level errors where *user friendlyness* is not the main concern. ``Assertion `!_iport_decl__p.hasPortValue()' failed.`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you get an output similar to:: system: somepath/HelloPackage/AT_MyAtomType.cpp:141: BipError& AT_MyAtomType::updatePortValues(): Assertion `!_iport_decl__aport.hasPortValue()' failed. It usually means that an instance of the atom type ``MyAtomType`` has reached a state where two (or more) transitions labeled by the same port (here ``aport``) are possible. You should get a warning at compilation:: [WARNING] In path/to/HelloPackage.bip: Transition from this state triggered by the same port (or internal) already exists : followed by an excerpt of the *potentially* faulty transition. Chances are that the guards on the transitions labelled by ``aport`` are not exclusive as they should be. ``XXXXX.cpp:000: error: ‘const_SOMETHING’ was not declared in this scope`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This error is the sign that you have at least of call to the ``SOMETHING`` function from a const context but the ``const_SOMETHING`` function implementation could not be found by the C++ compiler. Check: * that the external code has the ``const_SOMETHING`` function, if not, add it. * if the ``const_SOMETHING`` function is correctly defined, then check that the search paths given to the C++ are correct (see ``--gencpp-cc-I``) If you think you are not using the function ``SOMETHING`` from a const context, then, check your BIP code (the ``XXXXX`` in the C++ error message is a hint for a starting point). ``error: no match for ‘operator<<’`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you get an error similar to:: path/to/AT_AType.cpp: In member function ‘virtual std::string AT_AType::toString() const’: path/to/AT_Type.cpp:000: error: no match for ‘operator<<’ in ‘std::operator<< [with _Traits = std::char_traits] ... [C++ garbage] You are probably using data that the compiler can't [de]serialize. Two solutions exist for fixing this: * disable the serialization mechanism by using the ``--gencpp-no-serial`` command line argument. * add serialization support for your type by implementing the operator ``<<``. ``error: ‘my_XXX’ has a previous declaration`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ With ``my_XXX`` being a custom type name or an external function name. This usually means that one of your external *header* file gets included more than once, hence the duplicated declarations. You should always *include guards*:: #ifndef MY_CUSTOM_FILE_NAME__HPP #define MY_CUSTOM_FILE_NAME__HPP [the actual content of the header file] #endif // MY_CUSTOM_FILE_NAME__HPP