C++ Code Generation

The main backend of the compiler is the "Stan Math" (C++) backend. We represent C++ code with a data types and functions found in Stan_math_backend.Cpp.

We also define a sort of miniature embedded domain specific language (DSL) for using these types. These helper functions and operators are all in sub-modules of Stan_math_backend.Cpp, for example Stan_math_backend.Cpp.Expression_syntax.

These allow writing OCaml code which looks or feels more like the C++ it will generate. These constructs should be used when they improve clarity, and avoided when they make the code harder to read. When combined with good variable names, this can lead to code like lp_accum__.@?(("add", [Var "lp__"])) , which hopefully reads quite clearly as equivalent to the C++ lp_accum__.add(lp__).

After a Stan program is lowered to this type, it can be printed to C++ using Stan_math_backend.Cpp.Printing. This module uses Fmt, but keeps the question of how C++ should be formatted separate from the question of what the generated C++ is.

DSL Example

For example, lets say one wanted to generate the expression

(Eigen::Matrix<double,1,-1>(3) << 1, a, 3).finished()

This could be written down as the literal OCaml type it is:

(MethodCall (Parens (StreamInsertion (Constructor (Matrix (Double, 1, -1), [Literal "3"]), [Literal "1"; Var "a"; Literal "3"]), "finished", [], [])

Or, using the DSL constructs, the same expression could be written

let open Expression_syntax in
let open Types in
let vector = Constructor (row_vector Double, [Literal "3"]) in
let values = [Literal "1"; Var "a"; Literal "3"] in
(vector << values).@!("finished")