• Top
    • Documentation
    • Books
    • Boolean-reasoning
    • Projects
      • Apt
      • Zfc
      • Acre
      • Milawa
      • Smtlink
      • Abnf
      • Vwsim
      • Isar
      • Wp-gen
      • Dimacs-reader
      • Pfcs
      • Legacy-defrstobj
      • Proof-checker-array
      • Soft
      • C
      • Farray
      • Rp-rewriter
      • Instant-runoff-voting
      • Imp-language
      • Sidekick
      • Leftist-trees
      • Java
      • Riscv
      • Taspi
      • Bitcoin
      • Des
      • Ethereum
      • X86isa
      • Sha-2
      • Yul
      • Zcash
      • Proof-checker-itp13
      • Regex
      • ACL2-programming-language
      • Json
      • Jfkr
      • Equational
      • Cryptography
      • Poseidon
      • Where-do-i-place-my-book
      • Axe
      • Aleo
        • Aleobft
        • Aleovm
        • Leo
          • Grammar
          • Early-version
            • Json2ast
            • Testing
            • Definition
              • Flattening
                • Flatten-statements/branches
                • Simplify-flattened-if
                • Flatten-expression
                • Fenv-option
                • Flatten-fundecl
                • Fenv-const-add
                • Fenv
                • Fenv-const-lookup
                • Init-fenv
                • Const-fenv
              • Abstract-syntax
              • Dynamic-semantics
              • Compilation
              • Static-semantics
              • Concrete-syntax
      • Bigmems
      • Builtins
      • Execloader
      • Solidity
      • Paco
      • Concurrent-programs
      • Bls12-377-curves
    • Debugging
    • Community
    • Std
    • Proof-automation
    • Macro-libraries
    • ACL2
    • Interfacing-tools
    • Hardware-verification
    • Software-verification
    • Math
    • Testing-utilities
  • Definition

Flattening

Flattening of Leo.

Leo code is flattened by transforming it according to certain criteria. The transformation involves:

  • Constant propagation.
  • Constant folding.
  • Loop unrolling.
  • Function specialization.

The latter could be replaced by function inlining, but for now we want to preserve the functional decomposition of the program and therefore we do function specialization instead.

The flattening transformation starts with known values for the constant inputs of the main function. The transformation goes through the statements and expressions of the main function, transforming them as follows (the same transformations also apply to other functions; more on this below).

The known values of the constants are propagated where those constants are mentioned in expressions. That is, the names of the constants are replaced with value expressions.

This constant propagation may cause certain (sub)expressions to consists of operators applied to operands that are value expressions. In this case, the constants can be folded, i.e. the operands are evaluated to values, the operator is applied to values, and the result is turned into a value expression. This is constant folding.

Note that constant folding also applies to constant expression that only involve literals and operators (i.e. no named constants), which perhaps the Leo code writer has written that way for clarity. Those are folded by this transformation, as expected and desired.

When we encounter a constant declaration, assuming that the code is type-checked, the initializing expression must be constant. Therefore, constant propagation and folding must cause it to be turned into a value (more precisely, a value expression). Thus, we extend our table of known constant values with a new association, so that this constant can be also propagated.

When a loop is encountered, assuming that the code is type-checked, the starting and ending bounds must be reducible to values, in the same way as the initializing expressions of constant declarations. Thus, we know all the values that the loop counter goes through, and we unroll the loop. This is done by replicating the body block as many times as the values taken by the loop counter, in order. For each replicated block, the table of known constant values is extended with the known value of the loop counter, so that constant propagation and folding can be performed in each replicated block.

When we encounter a call of a function, say f, given that, as mentioned above, we do not do inlining, we proceed as follows instead. First, we transform its argument expressions. If f has no constant parameters, there is nothing else to do for the call. If f has one or more constant parameters, then, assuming that the code is type-checked, the values passed to these constant parameters must be known. We generate a version f1 of f that is specialized to these constant input values. This function f1 only has the non-constant parameters of f, and no constant parameters. The body of f1 is transformed in the same way as explained above; the starting point consists of the known values of the constant parameters.

The above transformations, which start on main, are performed on all the functions called directly or indirectly from main. As explained, this may cause the creation of new functions, specialized to certain values of the constant parameters; there may be multiple specialized versions of the same function, corresponding to different combinations of constant values.

Functions with constant parameters are eventually eliminated. Only their specialized versions survive the transformation. Function without constant parameters survive if they are called, directly or indirectly, from main; otherwise, they are eliminated, since they are dead code (when starting execution from main). The main function is transformed, besides in the body as above, also by removing its constant parameters, since they have been used to transform the body.

Overall, this transformation produces a new program that includes one or more functions (i.e. main plus zero or more functions), each of which has no constant parameters, no constant declarations, and no loops; it also does not contain any ``reducible expression'', i.e. an expression that could be evaluated to a value.

We formalize these flattening transformations as executable ACL2 functions that perform the flattening. These ACL2 functions return error indications when certain unexpected conditions happen, e.g. that the initializing expression of a constant declaration cannot be evaluated to a value. These conditions should be ruled out by type checking: that is, type checking, which also does ``constancy checking'', is meant to ensure that the code can in fact be flattened. We plan to prove formally that this is the case: that is, that the executable transformation functions never returns certain error indications when executed over type-checked code; this proof, which is similar to the one planned for the dynamic semantics, would also provide a major validation of the design of Leo.

More precisely, the flattening ACL2 functions may return two kinds of errors. A static error happens when some condition is violated that should be ruled out by type checking: we plan to prove that static errors never occur when flattening type-checked code, as mentioned above. A dynamic error happens when constant folding results in an erroneous operation, such as integer overflow: these conditions are not ruled out by type checking, and may cause actual errors when flattening type-checked code.

Subtopics

Flatten-statements/branches
Flatten statements and branches.
Simplify-flattened-if
Simplify a flattened conditional statement.
Flatten-expression
Flatten an expression.
Fenv-option
Fixtype of optional flattening environments.
Flatten-fundecl
Flatten a function declaration.
Fenv-const-add
Extend a flattening environment with a constant.
Fenv
Fixtype of flattening environments.
Fenv-const-lookup
Look up a constant in a flattening environment.
Init-fenv
Initial flattening environment.
Const-fenv
Fixtype of constant flattening environments.