ATC tutorial: ACL2 representation and generation of C
After describing
our ACL2 representation of the C
We do that via a simple example,
but present concepts that apply more generally.
However, this page only describes the basics of
representing and generating C
Our simple example is a C program consisting of a single function:
int f(int x, int y, int z) { return (x + y) * (z - 3); }
This function takes three
There is a one-to-one representational correspondence between C and ACL2 functions. That is, every C function is represented by an ACL2 function, whose name must be supplied to ATC in order to generate the corresponding C function definition (the exact call of ATC is described later in this page).
Thus, for the example program above,
we need an ACL2 function to represent
The names of the function and its parameters are represented by ACL2 symbols whose names are identical to the C identifiers:
(defun |f| (|x| |y| |z|) ...)
Note that we need the double bar notation because the names are lowercase, consistently with the C convention. If we omitted the double bars, we would be representing a different C function:
int F(int X, int Y, int Z) { ... }
This is because, without the vertical bars, the names of the symbols are uppercase. This is a valid C function, but does not follow the normal C naming conventions.
Package names are ignored for this purpose,
e.g. both
In the envisioned use of ATC, the user would write ACL2 functions with more conventional ACL2 names for both functions and parameters (i.e. without vertical bars). The user would then use APT transformations to turn those names into the form required to represent C names for ATC's purposes.
More details about the mapping from ACL2 names to C names are given in atc-tutorial-identifiers.
Given the representation of C
(defun |f| (|x| |y| |z|) (c::mul-sint-sint (c::add-sint-sint |x| |y|) (c::sub-sint-sint |z| (c::sint-dec-const 3))))
We represent the expression of the
The
In the envisioned use of ATC, the user would not write directly a function body of the form above. Instead, they would obtain that kind of function body via suitable APT transformations that turn (conventional) ACL2 types and operations into (ACL2 representations of) C types and operations, under suitable preconditions.
Given the use of add-sint-sint and sub-sint-sint
on the ACL2 parameters
That is, the guard must include explicit conjuncts that spell out the C types of the function's parameters. For the example function, these are as follows:
(defun |f| (|x| |y| |z|) (declare (xargs :guard (and (c::sintp |x|) (c::sintp |y|) (c::sintp |z|) ...))) ; more conjuncts, described below (c::mul-sint-sint (c::add-sint-sint |x| |y|) (c::sub-sint-sint |z| (c::sint-dec-const 3))))
When generating C code for
(defun |f| (|x| |y| |z|) (declare (xargs :guard (and (and (c::sintp |z|) (c::sintp |x|)) (and ... (c::sintp |y|)) ...))) ...) ; body as above
ATC infers the output type of a function
by performing a C type analysis of the function's body.
For the function
ATC requires that the ACL2 functions that represent C functions
are guard-verified (which implies that they must be in logic mode).
This ensures that the ACL2 functions that represent C operations
are always applied to values whose result is well-defined
according to [C].
It also ensures that sint-dec-const is always applied
to a natural number representable as an
However, this generally requires guards to have additional conditions,
besides the sintp conjunts discussed above.
It should be clear that a C function like
This should not be surprising.
It is fairly normal for programs to be correct
only under certain preconditions.
The example function
In a C program, there may be functions that receive data from outside the program. These functions should not assume any precondition on that data, and diligently validate it before operating on it. After validation, these C functions may call other C functions, internal to the C program, in the sense that they only receive data validated by the calling functions. The validation provides preconditions that may be assumed by the internal functions.
The example function
(encapsulate () (local (include-book "arithmetic-5/top" :dir :system)) (local (set-default-hints '((nonlinearp-default-hint stable-under-simplificationp hist pspv)))) (defun |f| (|x| |y| |z|) (declare (xargs :guard (and (c::sintp |x|) (c::sintp |y|) (c::sintp |z|) ;; -10 <= x <= 10: (<= -10 (c::integer-from-sint |x|)) (<= (c::integer-from-sint |x|) 10) ;; -10 <= y <= 10: (<= -10 (c::integer-from-sint |y|)) (<= (c::integer-from-sint |y|) 10) ;; -10 <= z <= 10: (<= -10 (c::integer-from-sint |z|)) (<= (c::integer-from-sint |z|) 10)) :guard-hints (("Goal" :in-theory (enable c::sint-integerp-alt-def c::sintp c::add-sint-sint-okp c::sub-sint-sint-okp c::mul-sint-sint-okp c::add-sint-sint c::sub-sint-sint))))) (c::mul-sint-sint (c::add-sint-sint |x| |y|) (c::sub-sint-sint |z| (c::sint-dec-const 3)))))
The proof is carried out on the ACL2 integers
obtained by unwrapping the C integers;
it uses
In the envisioned use of ATC,
the user may verify the guards of
Given the guard-verified ACL2 function
(include-book "kestrel/c/atc/atc" :dir :system) (c::atc |f| :output-file "f.c")
First, we must include ATC.
To avoid certain trust tag messages,
the include-book form could be augmented with a
The ATC tool is invoked on one or more ACL2 function symbols,
in this case just
The above invocation of ATC generates the C file, as conveyed by a message printed on the screen. The invocation also prints certain event forms on the screen; these will be described in atc-tutorial-events and can be ignored for now.
Opening the file
This example can be found in community book
On macOS or Linux, the generated file
For instance, one may add a file
#include <stdio.h> int main(void) { int x = 3; int y = -2; int z = 8; int r = f(x, y, z); printf("f(%d, %d, %d) = %d\n", x, y, z, r); }
This file calls the generated function on specific values,
and prints inputs and output.
The inclusion of
This file is found in community book
The two files may be compiled as follows on macOS or Linux:
gcc -o f f.c f-test.c
The
The executable may be run as follows:
./f
This prints the following on the screen:
f(3, -2, 8) = 5
Previous: ACL2 representation of the C