Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ghost constructors #11

Closed
wants to merge 2 commits into from

Conversation

RustanLeino
Copy link
Collaborator

This RFC proposes that each constructor of a datatype can be declared to be ghost.
Declaring a constructor to be ghost limits usage of that constructor to ghost contexts and auto-initialization.
It also restricts discriminators and destructors in compiled contexts, so that it is never necessary at run time to check if a value is of a ghost variant.

Copy link
Member

@robin-aws robin-aws left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting idea! I'm a bit worried that this is an unintuitive interpretation of the ghost keyword in this context though. I found it surprising to read that a ghost constructor can in fact be compiled and referenced at runtime, but you are just not allowed to "do anything" with the value.

I'd love to see a motivating example where just using /definiteAssignment:3 results in really sub-optimal code to model this manually.

been verified), and a use of the destructor `u.value` is compiled as
just `u`.

This lets a program allocate an array with element type `Unknown<T>`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds like this paragraph is the key motivation for this feature, so consider pulling it up to the beginning of the document. :)

Do we have any other use cases for ghost constructors in mind? It would be worth spelling out alternative ways of ensuring storage is always initialized. I recently discovered the undocumented new T[n] (i => ...) syntax for initializing arrays, which presumably could be used to explicitly assign an initial value when T is not auto-initializable. You would then have to manually spell out the pre-conditions that prevent such values from being read and used in any way, of course.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is the main motivating example. Here's another plausible use:

You may develop a model that uses several different variants of a datatype, say Red, Green, Yellow, and Blue. But in your running program, you only ever want to store Green or Blue values. To make sure of this, you can mark Green and Yellow as ghost constructors.

Comment on lines +36 to +39
A ghost-variant datatype is considered _not_ an equality-supporting
type. That is, the type does not obtain the type characteristic
`(==)`, and thus values of the type can be compared for equality only
in ghost contexts.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this an attempt to prevent ghost constructors from being used in any expression? I worry that just the fact that you can successfully read such a value at runtime already allows control flow that shouldn't be, and that this construction will be unsound in a subtle way. It sounds like the following program would be valid and halt at runtime, but I would expect the myArray[0] reference to be rejected statically, if I am trying to soundly avoid ever reading uninitialized storage:

datatype D = ghost G(x: int) | D0(y: int) | D1(z: int)

method Main() {
  var myArray := new D[5];
  var shouldNotReferenceThisAtRuntime := myArray[0];
  expect false;
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rules are intended to ensure soundness. You can read and copy these "uninitialized" values at run time, but there's no way to, at run time, determine whether or not you're holding a value that has been initialized.

The assignment shouldNotReferenceThisAtRuntime := myArray[0]; in your example is legal and causes no problem at run time. If the compiler decides to initialize myArray[0] with a G value, then myArray[0] is essentially some rubbish that you should do anything with. But it's fine and safe to copy this rubbish into another variable.

(Btw, not that it changes the example, but just to point out that the compiler is also free to initialize myArray[0] with a D0 or D1 value. Still, there's no way to determine at run time if that's what the compiler did.)

RustanLeino added a commit to dafny-lang/dafny that referenced this pull request Sep 20, 2022
This PR introduces ghost constructors for datatypes, as per RFC
dafny-lang/rfcs#11.

For motivation of this feature, see the RFC or the example `Stack` class
in `Test/comp/Uninitialized.dfy`.

## Feature description

* Any (and all) constructor of a datatype can be declared as ghost. This
is done by preceding the constructor name by the keyword `ghost`. (Note,
if the constructor has attributes, those are placed before the name of
the constructor, which is consistent with the attribute placement in
other declarations. For a ghost constructor, this means the attribute is
placed between the `ghost` keyword and the identifier that names the
constructor.)
* Compiled code may not explicitly name a ghost constructor. Moreover,
compiled code is not allowed to depend on the need to check if a
datatype value is of a ghost variant. Consequently, the verifier (and in
some cases, the resolver) will enforce that any discriminator (e.g.,
`Cons?`) or destructor (e.g., `tail`) is used only when the datatype
value is known not to be of a ghost variant. In other words, a
precondition for these operations (in compiled contexts) is that the
datatype value is not of a ghost variant.
* Even though a program cannot (in compiled contexts) explicitly create
ghost variants, Dafny might assign ghost variants as part of
auto-initialization. These "assignments" are performed by... doing
nothing! (Or nearly nothing.) In other words, since the verifier makes
sure that ghost variants are never looked at, the compiler feels free to
leave such variables as uninitialized.
* Uninitialized variables usually means asking for trouble. Ghost
constructors provides uninitialized storage and verifies that that
program never looks at uninitialized variables. Still, there are two
areas where uninitialized storage may affect execution (these two points
were not mentioned in the RFC):

- If storage is really uninitialized, then a run-time garbage collector
may not be able to trace pointers accurately.
- Dafny does not restrict `print`ing of uninitialized values. If a
program uses `print` with an uninitialized values, there is in general
no way to tell what will happen.

The compilers that Dafny supports are managed languages (with the
exception of C++, but C++ does not use a garbage collector; furthermore,
the C++ compiler currently supports only a subset of Dafny, and that
subset does not include ghost constructors). Since the target languages
are managed and type safe, there is no way for the Dafny compilers to
really leave storage uninitialized. Instead, values will typically have
a `null`-equivalent value. This means that garbage collection will work
and `print`ing will not crash the program.

As for printing, `print` should be considered a debugging facility or a
facility to make it easy to write student programs.

* The RFC mentions a compiler optimization where a datatype with exactly
one non-ghost constructor with exactly one non-ghost parameter can
compiled as just that one parameter. This PR does not implement that
optimization. A separate PR could implement this optimization along with
the similar optimization for tuples with ghost components. Ghost
constructors would benefit from the optimization, but do not depend on
it.


Closes dafny-lang/rfcs#11

<small>By submitting this pull request, I confirm that my contribution
is made under the terms of the [MIT
license](https://github.com/dafny-lang/dafny/blob/master/LICENSE.txt).</small>

Co-authored-by: Fabio Madge <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants