[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

PVS 7.1 Release Notes

PVS 7.1 introduces several new features and bug fixes. Some of the highlights include an improved installation (PVS is no longer a “tar bomb”), the concept of workspaces, improvements to the XML-RPC interface, some language changes, an improved tracking for TCC changes, improvements to strategy definitions, improvements to theory interpretations, and inclusion of Yices. There are many other changes not mentioned here; it is worth skimming through the rest of this chapter to see which are useful to you.


Installation Notes

The system is most easily installed by getting a tar file from http://pvs.csl.sri.com/download.shtml. Untar it in a directory of your choice (it will create a subdirectory), cd to the subdirectory, and run sh install.sh. This sets the path in various scripts, so that PVS knows where it is. It also byte-compiles the emacs source for your Emacs. If you move the PVS directory, or change your Emacs, or forgot whether you ran it already, then simply rerun it.

We strongly suggest getting a pre-built Allegro version, unless you have concerns with the Allegro runtime click-though license, in which case get one of the SBCL Lisp images. It is possible to build from sources, but it can be sensitive to the platform environment. If you decide to try it and run into problems, let us know at pvs-bugs@csl.sri.com. PVS 7.1 is built with Allegro CL 10.0 and SBCL 1.0.47. It is available for Linux 64-bit machines, and Mac 64-bit.

Note that the XML-RPC server is not (yet) available in SBCL.

The latest PVS Allegro supports a new GUI based on Microsoft Visual Studio Code. To experiment this new GUI, you will need to download VSCode-PVS (http://github.com/nasa/vscode-pvs), and have NodeJS (>=12.16.1 https://nodejs.org), Java JDK (>=1.8, https://openjdk.java.net), and Visual Studio Code (>=1.32.3, https://code.visualstudio.com).


New Features


PVS Contexts, Workspaces, and Libraries

Up to now PVS has had two notions of context, one which relates to directories (called a PVS context), and the other the usual list of declarations of type theory. The former are now referred to as workspaces, which are just directories with some sort of PVS file in them.

As with directories in a shell, there is a current workspace; other workspaces may be referenced as libraries. Within PVS names, the library is referred to by an identifier, and this is resolved to a workspace in one of three ways, in order:

Note that the second resolution is sensitive to what the current workspace is; in previous PVS versions this was implemented in such a way that the current workspace was treated in a special way, and changing workspaces meant “throwing away” much of the typechecking, etc. results.

In PVS 7, the information associated with a workspace has been encapsulated. This means that the current workspace is not so important. It’s more like the way a shell uses directories; even if you’re in a directory, you can run commands on files in other directories, simply by giving a (relative or absolute) pathname. In the same way, you can now typecheck a file from any workspace, even if it has never been seen by PVS before. It temporarily becomes the current workspace, the command is run, and then the current workspace is restored.

PVS can continue to be used exactly as in earlier versions, but there are several advantages to this change:


Development details

The current workspace is in the Lisp global *workspace-session*. If the *pvs* Emacs buffer is not in the prover or ground-evaluator, you can use (show *workspace-session*) to see the contents: this is an instance of the workspace-session class, with slots

Note that all of these slots were global variables in earlier versions of PVS; this is what is meant by encapsulation.

When PVS is started, it creates a workspace-session instance in *workspace-session*, reads the .pvscontext file, if it exists, and adds the workspace-session to the *all-workspace-sessions* list. When a file is parsed or typechecked in that workspace, the pvs-files and pvs-theories are updated accordingly.

To parse, typecheck, etc. a PVS file from a different directory, the macros with-workspace or with-pvs-file are used to temporarily change to the new workspace. If the associated workspace-session is already in *all-workspace-sessions* (based on the path), then it is simply used, otherwise a new one is created and added to *all-workspace-sessions*.

Macro: with-workspace path &rest forms

simply takes a path (string or pathname), and temporarily makes that the current workspace and processes the forms returning the value of the last one.

Macro: with-pvs-file vars pvsfileref body

takes a pvsfileref, which in general has the form dir/file.pvs#theory, though it can be as simple as a name. It is pulled apart, and if a dir is included, with-workspace is used, and the rest is split on the #, if it exists. vars is a list of names, and these are bound in order with the file, theory, and any others if they are provided (the idea is that there could be a #formula or #place following the #theory). The body is then executed with the names in vars bound accordingly. Note that one must be careful here; if pvsfileref is just a name, it may be referring to a file or a theory, depending on the context. Hence there is code such as that for show-tccs:

 
(defun show-tccs (theoryref &optional arg)
  (with-pvs-file (fname thname) theoryref
    (let* ((theory (get-typechecked-theory (or thname fname)))
           ...))))

Here a theory is expected, and if the theoryref is just a name, then fname is set to it, and thname is nil. This is why the theory is derived from (or thname fname).


PVS Language Changes


TCC Formulas and Associated Proofs

TCCs are generated during typechecking for a given declaration, and during a proof for a given branch. During typechecking, the name of a given TCC is derived from the declaration, followed by a number; e.g., if the declaration is named foo, then the TCCs will be named foo_TCC1, etc. Some declarations have many TCCs, with nontrivial proofs. When such a declaration is modified, it can generate insert, remove, or otherwise renumber the TCCs, and then associate the wrong proofs to them.

The new approach keeps track of where the proof came from, creating a tcc-origin instance, with the following slots:

The .prf file has been extended to include the same information with TCC proofs. When a PVS file is typechecked, the proofs are read from the corresponding .prf. TCC proofs are no longer matched by TCC name, but instead the kind and expr are used. Those that match exactly are directly used, any remaining proofs are installed (in order) in the remaining TCCs. This is obviously not perfect, as the corresponding exprs may not match, depending on the changes made to a declaration.

In addition to the above, TCC generation has been cleaned up, making the TCCs more readable.

As a result of these changes, many proofs involving TCCs need to be repaired. For shorter proofs, it’s often enough to step through the proof and make the corresponding changes. Sometimes, especially for more complex proofs, it’s not at all obvious how to repair the proof. In this case, the easiest thing is to run the version of PVS where the proof works in parallel with the newer version. In this way it becomes easy to find those steps that diverge, and to fix those.


PVS XML-RPC server


Introduction

The PVS GUI is an API for the Prototype Verification System (PVS). In the past, the PVS GUI was based on a modified version of the Emacs Inferior Lisp Mode (http://ilisp.cons.org/) interface. This generally works well, but there are some issues:

For these reasons, we decided to create a new API for a PVS GUI. We have several constraints we want to satisfy:

We started to create an Eclipse plugin for PVS, but found this to be difficult; there is really nothing in Eclipse to support things like proof windows, or the various popup buffers that PVS normally does through Emacs. Note that there is an eclipse subdirectory in the PVS Git sources, for anyone who wants to continue this work.

But we took a step back, and started fresh with Visual Studio Code, which so far has proved more flexible, and quicker for prototyping.

The basic architecture consists of a PVS server, with any number of clients. A client can make a request to the PVS URI, and PVS will return a response to that client. In addition, a client can start an XML-RPC server and include that URI with the request, which allows PVS to send requests to the client, e.g., to answer questions, provide file names, or simply get notifications.

In the long run, we expect to make Emacs an XML-RPC client as well, but for now, it uses the same ILISP interface. However, as each JSON method is defined (often based on the corresponding Emacs command), the same JSON will be returned to Emacs. This allows testing at the Emacs level, and provides an incremental way to move toward making Emacs an XML-RPC client.

Although PVS allows any number of clients, there is currently only one main PVS thread. This means that all clients would share the same proof session, etc. This may be useful for collaboration, or for switching between clients (i.e., different GUIs that provide different features). In the future we will explore the possibility of having separate threads associated with different clients, allowing different clients to simultaneously run different proofs, possibly in different contexts.

PVS provides an XML-RPC server when started with a -port value, e.g., pvs -port 22334, normally an unused port between 1024 and 65535. XML-RPC was chosen because it is supported by most modern languages, and we chose to implement the JSON-RPC 2.0 protocol within XML-RPC. Directly using JSON-RPC is possible, but it is not yet widely supported.

There is a single XML-RPC method provided by the PVS server, pvs.request, that takes a JSON-RPC request string, and an optional client URI, which is used to send requests to the client, providing a 2-way communication. Note that PVS does not keep the client URI after answering the request, thus clients may be killed and restarted at any time. In like manner, PVS can be restarted without needing to restart any clients, though it may be necessary to change context, retypecheck, etc. At the XML-RPC level, the return value includes the JSON-RPC response, the current PVS context, and the mode (lisp, prover, or evaluator). Thus if a given client has changed the context and started a proof, that information is included in the next request from a different client.

We chose JSON as the data interchange format over XML since it is more compact, and supported by most languages. In addition, there is a JSON Schema available, which we use to describe the API.

Error handling is done as follows. When an XML-RPC request comes in, PVS sets up a condition handler to catch any errors that may happen as a result of processing the request. If the request is badly formed, for a nonexistent method, or if the JSON-RPC request does not include an id, then a response is returned of the form

{"xmlrpc-error": string, "mode": string, "context": string}

If the request is well formed and includes an id, the method is invoked under a new condition handler, and the normal JSON-RPC response is given. This means that errors are returned even if the JSON-RPC request is a notification (without an id). Of course, the client is free to ignore such errors.


PVS JSON-RPC methods

There are only a few methods currently supported by PVS; a lot of effort was needed to implement the infrastructure. In particular, the prover was not really designed for a different API, and it was necessary to create hooks for generating a JSON representation of the current goal of a given proof.

The methods currently supported are listed below. Note that details about the possible return values are in the JSON Schema provided with PVS.

method: list-methods

This method simply lists the currently available methods.

method: list-client-methods

As described above, PVS may provide information or make requests to the client. This method lists all the JSON-RPC requests that PVS will invoke if it is given a URI at the XML-RPC level. Currently it consists of info, warning, debug, buffer, yes-no, and dialog. The JSON Schema gives details about the format.

method: help

Gives help for any given method returned by list-methods.

method: lisp

Simply sends a string to be evaluated by the PVS lisp interpreter, and returns a string with the result. Certainly an aid to debugging, but may also be useful for other purposes.

method: change-context

Changes the current context as with the Emacs change-context command.

method: typecheck

Typechecks a specified file. This returns a list of theories, each of which includes the declarations of that theory, as well as their locations.

method: names-info

This is a new method; given a PVS file, it returns an array of PVS identifiers, their location, the associated declaration (as a string), and the file and location where the declaration can be found. This can be used by the client to provide information about a given identifier when the mouse is hovering over that identifier. Clicking on that identifier could bring up the corresponding file and location.

method: reset

This interrupts any running process, and resets the system to the state where no proof or ground evaluator sessions are running. This may not clear up low-level server/client problems, as those are on a separate thread and more difficult to reset. We’re waiting for a situation where this is an issue.

method: prove-formula

Given a formula and a theory name, this starts an interactive proof. The result is the current goal consisting of a sequent and other fields, see the JSON schema for details.

method: proof-command

This sends the specified proof command to PVS, returning the current goal. Currently the proof needs to be started with prove-formula, though in principle any client (e.g., Emacs) could start the proof and a different client continue. It’s possible for this to allow collaboration on a single proof.


GUI

As described above, VSCode-PVS is a new PVS GUI based on Visual Studio Code. VSCode-PVS provides several features of modern code editors, including:


Prover Emacs UI

The prover has been significantly modified to generate structures suitable for sending to the GUI. As a means to test this, a new capability was added to PVS Emacs, making use of the same JSON forms as those sent to the GUI. By default, PVS uses the old display, simply printing the sequent in the *pvs* buffer, displaying the Rule? prompt, and reading the next prover command.

There are new proof displays available. These are new, and not well tested, please send feedback if you try them out. Keep in mind the distinction Emacs makes between frames, windows, and buffers. A frame is what most systems call a window; each frame can be moved around on the desktop, closed, resized, etc. Frames may be subdivided into windows, and each window displays a buffer. Note that buffers are there, even if they are not currently displayed; there are separate commands for listing buffers, killing buffers, etc.

There are 6 proof display styles available; no-frame, 0-frame, 1-frame, 2-frame, 3-frame, and 4-frame. As you might guess, the names say how many frames are involved. no-frame, the default, works as in the past. The rest create separate windows and frames for different parts of a proof session: the current goal, the command input, the proof commentary, and optionally the proof script.

The 0-frame uses the same frame as the PVS startup frame, and splits it into separate windows. The 1-frame creates a new frame for this purpose. The 2-frame puts the commentary in a separate frame, the 3-frame puts the commentary and proof script in separate frames, and the 4-frame puts all four parts in separate frames.

The commentary is used for the running commentary of a proof; information that is part of the proof session, but not really part of a given proof step. The command input is currently just a window into the *pvs* buffer, which still has the proof as before, even when displays are active. The sequent buffer has the feature that hovering the mouse over an identifier shows the corresponding declaration; this can be very helpful in proofs.

The different parts of the proof display have associated faces, and can be customized. Do M-x customize and search for pvs to find all customizable faces.


PVS Identifier Tooltips

A new feature of PVS, developed partly for the new GUI, is the ability to associate tooltips with each PVS identifier of a PVS file or proof sequent. These tooltips are only available in typechecked files. They are automatically available in the GUI after typechecking; in Emacs, run M-x pvs-add-tooltips in any typechecked buffer (including the prelude) and then move the mouse over identifiers in the buffer to see their types. Clicking middle takes you to the file with the cursor at the declaration.


Proof Command Definitions

The proof command facility has been revamped, primarily in the argument handling. This section is for those who write strategies.

Just to review, strategy definitions such as defstep have required, optional, and rest arguments, e.g.,

 
(defstep foo (a &optional b c &rest d) ...)

Invocations of foo require the first argument; if there is a second argument it is bound to b, a third argument to c, and any remaining arguments are bound to d. This is similar to Common Lisp, but in PVS the optional and rest arguments may also be given as keywords, so foo could be invoked as either of the equivalent forms

 
(foo 3 5 7 11 13)
(foo 3 5 :d (11 13) :c 7)

In order to add a new argument to a low-level command, (e.g., the let-reduce? flag was added to assert), then to make this available to other commands such as grind meant adding it and the corresponding documentation to those commands. This is obviously error-prone. Recently we wanted to add the actuals? argument of replace to grind, in order to allow grind to work in type and actual expressions. The problem is that grind invokes replace*, which has a &rest fnums argument; this does not allow new arguments to be added without modifying existing proofs.

To solve this immediate problem we added the &key indicator. It is similar to the &optional indicator, but the arguments must be provided as keywords. Hence replace* could now be rewritten from

 
(defstep replace* (&rest fnums) ...

to

 
(defstep replace* (&key actuals? &rest fnums) ...

Existing proofs would not break, but new proofs could invoke replace* with an :actuals? t argument to have replacement happen inside of types and actuals.

But this only solves part of the problem; propagating this argument to strategies such as grind is still error-prone. To deal with this, we added another indicator: &inherit. With this, replace* can be defined as

 
(defstep replace* (&rest fnums &inherit replace) ...)

And now replace* automatically inherits all keyword arguments from replace. Not only that, but any invocations of replace within the body of the defstep automatically include keyword invocations of the replace call. In effect, where the body was written simply as (replace y), it is replaced in the actual command by

 
(replace y :dir dir :hide? hide? :actuals? actuals?
           :dont-delete? dont-delete?)

Note that this inherited not just the actuals? argument, but all the others as well. Note also that if a new argument is added to replace, it will be automatically inherited by replace*.


Future Work

There is still work to be done; currently optional and key arguments allow a default, but we want to in addition allow :documentation and :kind keywords, even for required arguments. The documentation will be used to document the arguments, rather than have them in the main documentation of the proof command. For optional and key arguments, this documentation will then propagate, so that, e.g., the documentation for replace* directly explains the actuals? argument, without having to look up replace.

The :kind will be used to support refactoring (among other possibilities). One problem with refactoring currently is that proofs are kept as proof scripts, and any types, expressions, etc. are given as strings. Thus, for example, a command such as (expand "foo") will resolve the name foo, and expand occurrences of it within the current sequent. This is the case even if foo is overloaded, and has three definitions in the sequent. Note that foo is resolved by the prover, and the resolutions are used in the subsequent expansions, but then discarded. If now the user decides that overloaded foo is confusing, and wants to name them apart, there is no way to know which ones to name apart in proof scripts without rerunning them.

The :kind keyword will be used to associate a kind with each argument, which in cases such as above would invoke functions that generate the resolutions and cache the resolution information with the proof, in a way that it may be used subsequently for refactoring, etc.


Detailed Description

The basic idea and motivation are above, the rest of this section goes into more details for those wanting to write new strategies.

The formal arguments list for a new prover command is in a specific order: required, optional, key, rest, and optional. The actual syntax is

prover-args ::= {var}*
                [&optional {var | (var initform)}*]
                [&key {var | (var initform)}*]
                [&rest var]
                [&inherit {cmd | (cmd :except var+)}*]

Required, optional, and rest arguments work exactly as detailed in the prover guide. Key arguments are similar to optional arguments, but may only be specified by keyword, not by position.

The inherit argument is fundamentally different. A proof command inherits arguments from other proof commands. This can only be done for proof commands that are directly referenced in the body; for example, grind inherits from replace*, not replace, because it does not directly call the latter. There are two aspects to inheriting arguments from a command. The first is that the command being defined takes the union of all the arguments of its own and inherited commands. The second, is that these inherited arguments are propagated to any calls of inherited commands.

The inherited arguments are always either optional or key arguments; they are always treated as key. Hence the order of inherited arguments is not an issue, though there is a possible issue if the names of arguments clash with different meanings. This can be controlled to some extent by using the :except form, specifying the arguments to be ignored of an inherited command. If there are more than one unignored arguments with the same name and different default values, the first is taken as default. Again, this can easily be controlled, for example, if we have the forms

 
(defstep foo (x &optional (a 3) &key (b 5) (c 7)) ...)
(defstep bar (y &optional (b 7) &key (a 11) (c 13)) ...)
(defstep baz (z &key (a 13) &inherit (foo :except c) bar) ...)

Then baz gives its own default to a, and takes foos default for b and bars default for c.

Propagating the arguments to calls is relatively straightforward. Using the above as examples, if the body of baz has an occurrence of (bar m), it is simply replace by (bar m :b b :a a :c c) and (foo n) is replaced by (foo n :a a :b b). Note that multiple invocations may be made to, e.g., foo, and all of them are replaced. Note also that, e.g., one could be as above, while the second invocation is (foo :c 31 :a 37), which gets expanded to (foo :c 31 :a 37 :b b).

The PVS Emacs command M-x help-pvs-prover-strategy (C-c C-h s) now includes the expanded argument list and definitions, as well as the original forms. This can be helpful in understanding how the prover arguments work.

This change has little impact on existing proofs, though in the regression tests it was found that a couple of strategies defined in the NASA libraries were not quite correct, but the old strategy mechanism simply ignored extra arguments. Now those generate an error.


Positive Type Parameters

PVS treats positive type parameters specially in datatypes, so that, e.g., cons[int](1, null) = cons[nat](1, null), but this did not extend beyond constructors and accessors. Now PVS treats all definitions accordingly. The basic idea is that if a given definition does not depend directly on the type, and only on the values, then it is safe to ignore the type parameter - though typechecking may still generate a TCC.

Thus, for example, length[T]((: 2, 3, 5 :)) is 3, regardless of which numeric subtype T may be, though unprovable TCCs may result (e.g., if T is even). Similarly, nth and every depend only on the arguments, not on the types. An example of a definition that depends on the types, not merely the arguments, is

 
th[T: type from int]: theory
 ...
 foo(x: T): int = if (exists (y: T): y > x) then x else 0 endif
 ...
end th

This change can have an impact on existing proofs, though mostly it makes them more direct - some proofs involving recursive functions, e.g., length[int](x) = length[nat](x) require convoluted proofs.


Typepred Extension

The typepred prover command was extended to include functional typepreds. Thus if f has type [D -> {x: R | p(x)}], then the proof command (typepred "f") would generate a hypothesis of the form FORALL (x: D): p(f(x)). Note that some commands such as skolem, take a flag that causes typepreds to be generated - this would also include these functional typepreds.


TCC Ordering

TCCs that depended on conjunctive forms were generated in some cases in reverse. This has no bearing on soundness or correctness, but some meta-analysis of PVS was made more difficult because of this, so it was fixed.


Yices

The yices prover commands have been fully integrated into PVS, and Yices versions 1 and 2 are included in the distribution.


Theory Interpretations

Theory interpretations have been improved in a few ways. First, if an uninterpreted type or constant has declaration formals, they must be included in the mapping, e.g.,

eq [A:TYPE+]: [A -> [A -> bool]]

logic_sttfa_pvs : THEORY = logic_sttfa {{
    connectives_sttfa_th := connectives_sttfa_pvs,
    eq[T:TYPE+](x:T)(y:T) := x=y
  }}

Second, the context is updated with the theory without mappings when typechecking the RHS. This means that it is not necessary to have two importings in order to specify the RHS, and gives access to declarations of that context that are not being interpreted.


Incompatibilities

There were a couple of corner cases where TCCs were not being generated. In fixing this, it was decided to rewrite the code to make TCCs easier to understand and work with.

There are three primary sources of incompatibilities with this release. This first is due to more rigorous checking of arguments in proof commands. In the past, if there were left over arguments after pairing command arguments with their invocation, they were simply ignored. Now an error is invoked. Generally these are easy to debug, and they usually indicate a programming error to begin with.

TCC ordering can affect formula numbering (e.g. foo_TCC1 and foo_TCC2 could be swapped, and within a proof, the branches may be swapped. In the regression tests, this was fairly rare.

The addition of more typepred information in proofs leads to additional hypotheses, and this can cause formula numbers to be shifted.

In the past, the typechecker was a little strict in creating new variables when a formula change was made (e.g., expanding a definition). It tends to keep the existing variable name more often. Occasionally, this means a reference to, e.g., “i_1” should be changed to “i”.


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Sam Owre on August 19, 2020 using texi2html 1.82.