• Top
    • Documentation
    • Books
    • Boolean-reasoning
    • Projects
    • Debugging
    • Std
    • Proof-automation
    • Macro-libraries
    • ACL2
    • Interfacing-tools
    • Hardware-verification
    • Software-verification
      • Kestrel-books
        • Crypto-hdwallet
        • Apt
        • Error-checking
        • Fty-extensions
        • Isar
        • Kestrel-utilities
        • Set
        • Soft
        • C
        • Bv
        • Imp-language
        • Event-macros
        • Java
          • Atj
            • Atj-implementation
            • Atj-tutorial
              • Atj-tutorial-deep
              • Atj-tutorial-ACL2-values
              • Atj-tutorial-native-functions
              • Atj-tutorial-deep-guards
              • Atj-tutorial-ACL2-terms
              • Atj-tutorial-evaluator
              • Atj-tutorial-background
              • Atj-tutorial-ACL2-environment
              • Atj-tutorial-translated
              • Atj-tutorial-shallow
                • Atj-tutorial-tests
                • Atj-tutorial-customization
                • Atj-tutorial-motivation
                • Atj-tutorial-deep-shallow
                • Atj-tutorial-screen-output
                • Atj-tutorial-shallow-guards
                • Atj-tutorial-simplified-uml
                • Atj-tutorial-aij
            • Aij
            • Language
          • Bitcoin
          • Ethereum
          • Yul
          • Zcash
          • ACL2-programming-language
          • Prime-fields
          • Json
          • Syntheto
          • File-io-light
          • Cryptography
          • Number-theory
          • Lists-light
          • Axe
          • Builtins
          • Solidity
          • Helpers
          • Htclient
          • Typed-lists-light
          • Arithmetic-light
        • X86isa
        • Axe
        • Execloader
      • Math
      • Testing-utilities
    • Atj-tutorial

    Atj-tutorial-shallow

    ATJ tutorial: Shallow Embedding Approach.

    This tutorial page introduces the use of AIJ with the shallow embedding approach. This approach provides much richer features than the deep embedding approach. These features will be presented throughout several tutorial pages. This page describes the more basic features.

    AIJ's Role

    In the shallow embedding, AIJ plays a more limited role than in the deep embedding. The shallow embedding uses AIJ's default Java representation of the ACL2 values and AIJ's natively implemented ACL2 functions, but it does not use AIJ's representation of the ACL2 terms and AIJ's evaluator. AIJ's representation of the ACL2 environment is used partially by the shallow embedding, which uses the package definitions but not the function definitions.

    Thus, in the shallow embedding only a portion of the AIJ API is used, namely the one to build and unbuild ACL2 values, the one to invoke the native implementations, and the one to build package definitions. The portion to build function definitions and to execute functions via interpretation is not used.

    ATJ's Role

    In the shallow embedding, ATJ plays a different role, compared to the deep embedding. The only commonality is that, in both shallow and deep embedding, ATJ generates Java code to build the ACL2 package definitions via AIJ's API.

    However, in the shallow embedding, ATJ does not generate Java code to build the ACL2 function definitions via AIJ's API, because ACL2 functions have a different Java representation in the shallow embedding. Furthermore, in the shallow embedding, ATJ does not generate Java code that provides a wrapper API to invoke AIJ's interpreter, because ACL2 functions are not executed via interpretation in the shallow embedding.

    Instead, in the shallow embedding, ATJ generates Java code that ``resembles'' the code of the ACL2 functions. ATJ generates, in essence, a Java method for each ACL2 function. The method takes the same inputs and returns the same outputs as the corresponding ACL2 function (`same' in the sense of the Java representations of such inputs and ouputs); it calculates the outputs from the inputs by mimicking the computation steps performed by the ACL2 function. This is best clarified through an example.

    Example of Generated Code

    Consider the factorial function example in atj-tutorial-deep:

    (defun fact (n)
      (declare (xargs :guard (natp n)))
      (if (zp n)
          1
        (* n (fact (1- n)))))

    To generate shallowly embedded Java code for that function, include ATJ via

    (include-book "kestrel/java/atj/atj" :dir :system)

    as before, and call ATJ via

    (java::atj fact :deep nil :guards nil)

    where :deep nil specifies the shallow embedding approach and :guards nil specifies not to assume the guards' satisfaction (more on this in subsequent tutorial pages).

    As in the deep embedding approach, two Java files, Acl2Code.java and Acl2CodeEnvironment.java, are generated (in the current directory), each of which contains a single Java class with the same name (without .java); the first class is public, the second package-private. Also as in the deep embedding, the files import all the (public) AIJ classes, which are in the edu.kestrel.acl2.aij Java package, and a few classes from the Java standard library.

    The Acl2CodeEnvironment class starts with a package-private build() method that calls a number of methods to define ACL2 packages, as in the deep embedding approach; but this method does not call any methods to define ACL2 functions. This method is followed by the declarations of the (private) methods that it calls, as in the deep embedding approach: these methods that define the packages are identical in the shallow and deep embedding approaches.

    As in the deep embedding, the Acl2Code class starts with a static initializer to build the ACL2 environment; see atj-tutorial-deep for details.

    The static initializer in the Acl2Code class is followed by the same empty initialize() method as in the deep embedding; see atj-tutorial-deep for details. However, unlike the deep embedding approach, there is no call(Acl2Symbol, Acl2Value[]) method, because, as explained below, (the Java representations of) the ACL2 functions are called directly as methods.

    After the initialize() method, there are a number of nested static classes, each of which corresponds to an ACL2 package. These are a strict subset of the packages whose Java representation is built in the static initializer; for instance, there is no nested class for the "KEYWORD" package (in this example; in other examples, there is such a class).

    The nested class ACL2 is for the "ACL2" package. Its fact(Acl2Value) method is generated from the fact function; the correspondence of its Java body with the ACL2 unnormalized body (visible via (ubody 'fact nil (w state)), or via :props fact) should be apparent. This method is in the ACL2 class because the symbol-package-name of the (function) symbol fact is "ACL2". For the same reason, this class includes a method zp(Acl2Value) generated from the zp function; again the correspondence of its Java body with the ACL2 unnormalized body should be apparent. The nested class ACL2 also contains methods that correspond to Java primitive functions (which have no unnormalized body) such as binary_star(Acl2Value,Acl2Value), whose Java body calls AIJ's native Java implementation. (The reason for introducing wrappers of the native Java implementations such as binary_star(Acl2Value,Acl2Value) is that they are more readable than the qualified method references in Acl2NativeFunction; presumably a JIT could remove the run-time penalty of the wrappers.)

    The nested class COMMON_LISP is for the "COMMON-LISP" package. Its not(Acl2Value) method is generated from the not function; again, the correspondence of this method's body with the unnormalized body of not should be apparent. This method is in the COMMON_LISP class because the symbol-package-name of the (function) symbol not is "COMMON_LISP".

    Note that the function acl2::fact is translated to the method ACL2.fact(...), the function acl2::zp is translated to the method ACL2.zp(...), the function common-lisp::not is translated to the method COMMON_LISP.not(...), and so on: package to class, double colon to dot, and name to method. Running ATJ with :verbose t as explained in atj-tutorial-screen-output displays the exact correspondence between ACL2 package names and Java class names, and between ACL2 function symbol names and Java method names. Since ACL2 is more liberal about the characters allowed in package and symbol names than Java is about the characters allowed in class and method names, sometimes there must be differences between the ACL2 and Java names.

    Since the zp(Acl2Value) method is in the ACL2 class while the not(Acl2Value) method is in the COMMON_LISP class, it seems that the body of zp(Acl2Value) should call COMMON_LISP.not(...) instead of just not(...). However, in ACL2 the package "ACL2" imports the symbol with name "NOT" from the package "COMMON-LISP". This is reflected in the Java code generated by ATJ: the class ACL2 has a method not(Acl2Value) that calls the method in the COMMON_LISP class. This is the case for all the methods generated from function symbols imported in packages: the importing package has a synonym method that calls the one in the class corresponding to the symbol-package-name of the function symbol. Introducing these synonym methods may be the closest way to mimic ACL2's ability to refer to the same symbol using different package prefixes: while acl2::not and common-lisp::not denote the same ACL2 symbol, in the generated Java code the method calls ACL2.not(x) and COMMON_LISP.not(x) have the same effect (presumably a JIT could remove the run-time penalty of these synonyms).

    After the nested classes, there are three constants (i.e. final static fields) for the ACL2 integers 0, 1, and -1. These are all the quoted constants that occur in the unnormalized bodies of fact and of the non-primitive functions directly or indirectly called by fact, namely zp and not; these are discussed also in atj-tutorial-deep. The quoted constants 1 and -1 occur in fact, and the quoted constant 0 occurs in zp; no quoted constant appears in not.

    Example of External Code

    Similarly to the example in atj-tutorial-deep, external Java code must call initialize() before calling (the Java methods corresponding to) the ACL2 functions, and also before using AIJ's API to build the values to pass as arguments to the functions.

    The following simple example of external Java code is similar to the one in atj-tutorial-deep, with a few differences explained below:

    import edu.kestrel.acl2.aij.*;
    
    public class Test {
        public static void main(String[] args)
            throws Acl2UndefinedPackageException {
            Acl2Code.initialize();
            Acl2Value argument = Acl2Integer.make(100);
            Acl2Value result = Acl2Code.ACL2.fact(argument);
            System.out.println("Result: " + result + ".");
        }
    }

    With the shallow embedding, we do not build the symbol that names the function. We build the arguments of the function (just one in this case), but not an array to hold them. And instead of calling Acl2Code.call(function, arguments), we call directly the method that corresponds to the ACL2 function.

    Example of Compiling and Running the Code

    The code can be compiled and run in the same way as in atj-tutorial-deep.

    Java Stack Space Considerations

    The Java stack space considerations made in atj-tutorial-deep partially apply to the shallow embedding approach. The Java methods generated from recursive ACL2 functions are recursive, unless the ACL2 functions are tail-recursive (in which case, as described later, the generated Java methods use loops instead of recursion). This is the case for the method generated from the fact function, which is (non-tail-)recursive. If these recursive methods are called with sufficiently large numbers, the JVM may run out of stack space.

    However, this should happen more rarely than in the deep embedding. The reason is that, in the shallow embedding, the Java stack frames correspond to the ACL2/Lisp stack frames, i.e. there is one for every method/function call. In contrast, when AIJ's recursive interpreter is run, there may be many Java stack frames for each ACL2 function, corresponding to the terms and subterms of the function body: the frames are for the recursive calls of the AIJ interpreter.

    Furthermore, as mentioned above, if an ACL2 function is tail-recursive, the generated Java method uses a loop. ATJ uses the well-known technique of tail recursion elimination. Thus, in an ACL2 development, one can write tail-recursive functions, or perhaps use APT's tail recursion transformation tailrec to turn non-tail-recursive functions into equivalent tail-recursive ones, prior to invoking ATJ.

    Test Generation

    ATJ's test generation facility is available for the shallow embedding in the same way as it is available for the deep embedding. The examples in atj-tutorial-tests, which were described for the deep embedding, also apply to the shallow embedding. The generated testing code is slightly different in the way that it calls the functions being tested; the difference is the same as illustrated in the example above of external code that calls the Java method for the factorial function.

    Previous: Generation of Tests

    Next: Guards in the Shallow Embedding Approach