LustreFunDefsSMT-level definitions of the recursive functions applied to quantified variables
A recursive function is compiled by unrolling its definition a number of times in the transition system, with the outputs of every instance tied to a functional symbol f.out.__function_of_inputs, and the recursive calls past the unrollings either left unconstrained or abstracted by the contract of the function (see LustreTransSys). A call applied to quantified variables has no instance to be unrolled: it is compiled to an application of the functional symbol, and the function is then given a definition by this module, as an SMT-LIB define-funs-rec block, so that the solver knows it at every argument the quantifier ranges over. The definition also ties the instances of the function to its body past their unrollings, so no counterexample is spurious for a defined function (see RecUnrolling).
Only a function whose recursive calls past the unrollings are left unconstrained is defined: one with no contract to abstract it with (no guarantee and no mode with an ensure, whether explicit or from a refinement type of an output; assumptions do not count) or declared transparent, or, outside of compositional analyses, any function not declared opaque (see contract_abstracts; LustreUserFunctions only accepts a quantified call to such a function). An opaque function with a contract is always abstracted by it, and a translucent one is in a compositional analysis.
The definition of a function is built from the equations of its body. A call to a function of the same recursive group applies the symbol of the callee under the guard of the termination checks of the call, and yields an arbitrary value (f.out.__undefined_call) otherwise: the definitions are then well-founded whether or not the measure of the function actually decreases, so they always have a model and cannot make the transition system inconsistent. The termination checks themselves remain properties of the transition system. A call to a recursive function of another group applies the functional symbol of the callee, which either has a definition of its own or is uninterpreted; a call to an imported function applies its functional symbol; and a call to any other function applies the symbols of a definition of that function (f.out.__function_definition), in a block of its own. Inlining the body of such a function instead would copy it at every call, and the definition of a function that calls another twice, which calls another twice, and so on, would grow exponentially with the depth of the calls. The symbols are not the functional symbols of the function, which the transition system ties to the instances of the function, where a contract may abstract it.
Every function of a recursive group is defined, or none is. A group is defined only if a function of it is applied to quantified variables, or called by the definitions of such a group, and each of its functions is eligible (not abstracted by its contract, as above) and definable: its body is a total function of its inputs, without assertions, oracles, array-typed variables or calls to nodes, and the same holds for the functions it calls that are neither recursive nor imported. Definitions are only produced for the solvers that support them (Z3 and cvc5) and when no logic is set with --smt_logic; a function that is applied to quantified variables but not defined, for any of these reasons, has its functional symbol left uninterpreted, and LustreTransSys warns about it.
type def = UfSymbol.t * Var.t list * Term.tA function definition: the symbol, its formal parameters and its body
type block = def listA block of mutually recursive definitions, defined together
val empty : tNo definitions
true iff recursive functions are to be defined at the SMT level: the SMT solver supports recursive function definitions, and the logic is inferred from the system rather than set explicitly.
val contract_abstracts : LustreNode.t -> boolWhether the contract of a recursive function stands in for its body past its unrollings, so that its recursive calls there are abstracted by the contract rather than left unconstrained: the function has a contract to abstract it with, and is opaque, or translucent in a compositional analysis
val compute : adt_junk_ufs:UfSymbol.t list -> LustreNode.t list -> tCompute the definitions of the recursive functions of the given nodes that a call applied to quantified variables applies (see above). adt_junk_ufs are the symbols giving selectors their value outside of their constructor, which a definition may apply.
val compute_all : adt_junk_ufs:UfSymbol.t list -> LustreNode.t list -> tCompute the definitions of every recursive function of the given nodes that is definable, whether or not a call applied to quantified variables applies it, and whether or not its contract abstracts it. These are not given to the engines, whose analysis unrolls the functions instead: the supervisor uses them to evaluate the recursive calls of a counterexample that are past the unrollings or abstracted by a contract, which are at concrete arguments (see RecUnrolling).
val compute_evaluable :
adt_junk_ufs:UfSymbol.t list ->
NodeId.Set.t ->
LustreNode.t list ->
tCompute the definitions of the given recursive functions, and of the functions their definitions call, whether or not the contract abstracts them, provided they are definable. These are not given to the engines either: the transition system evaluates the calls to the functions at constant arguments with them (see LustreTransSys).
true iff the functional symbols of the given function are defined
The blocks of definitions the given function's own depends on, followed by its own, in dependency order; empty if the function is not defined. The blocks it depends on include the definitions of the functions that are neither recursive nor imported it calls.
val ufs_of_node : t -> NodeId.t -> UfSymbol.t listThe uninterpreted symbols applied by the blocks of the given function, to be declared before them; empty if the function is not defined
val helper_ufs_of_node : t -> NodeId.t -> UfSymbol.t listThe symbols the blocks of the given function define for the functions that are neither recursive nor imported; empty if the function is not defined. A solver that is given instances of the defining equations rather than the definitions expands their applications into the bodies of their definitions, as the definitions used to be built.
bounded_below ms states that every component of the measure ms is non-negative