Génération de code › C++

Génération de code

Cette page n’est pas encore traduite : voici la version anglaise.

C++

archec gen main.arche --target cpp writes C++17 from the model, for a hosted target: Linux, macOS, or an RTOS with a C++ standard library and threads. The c target is the one for a microcontroller with no heap; this one is for the computer that runs the bigger parts, and is written the way C++ is written there.

archec gen examples/autonomous-car/main.arche --target cpp -o generated
cmake -S generated/cpp -B build && cmake --build build
ARCHE_RUN_MS=500 ./build/adas

A component framework of your own can have its own C++ generator, as a plugin (plugins).

What it writes, in cpp/#

File What it is
arche/rt.hpp What a part needs from the platform: arche::now_ns() (steady clock), wait_ns, log; the little-endian put/get the codecs use; min, max, clamp in the common type of their arguments; arche::Worker, a thread of the model.
arche_types.hpp/.cpp In namespace model: constants as inline constexpr (durations in ns as std::uint64_t), enums as enum class, unions as std::variant (a case with no payload is std::monostate), optional fields as std::optional, arrays as std::array (std::vector when unsized), structs and messages as structs. A message that is a value (message PING : uint32) holds it in value. Each message with a fixed layout has kSize, encode(buf) and decode(buf, len) on the same wire layout as the C, the DBC and the Rust: the same bytes.
<part>.hpp/.cpp A class per component that behaves (not the hardware).
<binary>/main.cpp With a deployment: the program of a binary.
<binary>/hooks.hpp, hooks.cpp What leaves the binary, and what your drivers call. hooks.cpp is written once: yours.
CMakeLists.txt A library of the parts, an executable per binary, -Wall -Wextra -Werror (-DARCHE_WERROR=OFF to relax).

Names: a type or a message is PascalCase (BRAKE_CMD → BrakeCmd; a message whose name would be a type's, as kacao's POINTING beside Pointing, gets Msg), a field or a member snake_case, a constant, an enumerator or an event SCREAMING_SNAKE, a state as written.

A component#

model::Adcu adcu;
adcu.send_can_brake_brake_cmd = [](const model::BrakeCmd &m) { /* to the bus */ };
adcu.start();                                   // watchdogs armed, the initial state entered
model::EngageReq req{true};
adcu.fire(model::Adcu::Event::ENGAGE_REQ, &req);
if (adcu.in(model::Adcu::State::Autonomous)) { … }
  • Its variables are public members, with the model's initial values.
  • Its functions are member functions. A ```arche body is translated; a Python body is translated when it says what the action language says (most simple ones do), else it is a TODO with the Python under it.
  • What it sends is a std::function member per port and message (send_<port>, or send_<port>_<message> when the port sends several); one that is not set sends nothing.
  • Its state machine is an enum class State and a switch, the C's algorithm in a class, in the order the specification gives: fire(event, msg) takes the transition the event triggers, innermost state first (the message's fields are the names its guards and actions read), go_to(state) runs the on-exits and on-enters, in(state) is true in a state or a state inside it.
  • Timers: each after transition and each watchdog is a timer; next_due() says when the next runs out, timeouts() runs those that did, kick(WD_X) restarts a watchdog, on_timer (when set) is told each time one is armed.
  • Deadlines: late is how late the trigger being handled is; a transition's deadline reads it and runs its on-deadline instead.

A deployment#

With a deployment, each binary that says language cpp (each binary, when none does) gets a main.cpp:

  • an instance of each part the binary runs;
  • an arche::Worker — a std::thread — per thread of the model: it runs its parts' main and the functions it runs once, then each period its step, the messages other threads post to it, and its parts' timers when they run out;
  • each link between two parts of the binary: the receiver's thread delivers the message (its transition, the port's on-receive, its handlers);
  • each link out of the binary: the message encoded, then arche_external_send(link, msg, id, buf, len) in hooks.cpp (the frame id is the route's id, as in the C); a message from outside comes in through arche_receive(link, msg, buf, len), which your driver calls;
  • main runs until SIGINT or SIGTERM, or for ARCHE_RUN_MS milliseconds.

A thread's priority and policy are written as a comment beside its worker: set them on native_handle() in arche_hooks_start() if they matter (that is pthread_setschedparam on Linux).

Verified#

test/cppgen.test.js builds examples/autonomous-car with CMake and -std=c++17 -Wall -Wextra -Werror and runs it; compiles every example and the difftest fixtures with -Werror; encodes a message with an enum, a union, an optional field and an array in C++ and in C and compares the bytes (and decodes the C's); runs door.arche's machine in C and in C++ message by message and compares the state and the variables after each one; and drives a rover's nested machine, guards, action, on-enter, handler and watchdog.

Not done#

  • A sensor's noise and an actuator's lag and clamping (realism.ts) are in the C only.
  • A message that says its wire format bit by bit (at, bits, scale) has its struct but no C++ codec: the C (--target c or wire) has it.
  • --trace and --monitors are C and Rust only.
  • Thread priorities, policies and cores are comments, not applied.
  • An AUTOSAR Adaptive flavour (ara::com proxies and skeletons) is not written.