Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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
Reads clauses on methods #6
base: master
Are you sure you want to change the base?
Reads clauses on methods #6
Changes from 6 commits
a4791e0
dca81f2
47df41d
b3274cc
5de90e6
668cf87
a34635d
15e4731
1762c7f
a69a452
File filter
Filter by extension
Conversations
Jump to
There are no files selected for viewing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This section doesn't describe any use-cases that require something other than
reads {}
, so why not introduce a more restricted construct likethreadsafe
? That gives less freedom to the programmer, so less chance of confusion, and you could relax the associated checks in the future, for example to allow reading passed in arguments as long as they are immutable or owned by the callee.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other
reads
frames are necessary on internal methods (i.e. not exposed to external code) in the control flow though. For example,FibonacciMemoizer.Get
has to declarereads this
, because otherwise the default spec ofreads *
would violate the tighterreads {}
frame ofSafeFirstNFibonaccis
.I would actually argue that reusing an existing concept in a very similar additional context is a lower cognitive burden on programmers than a new concept like
threadsafe
(and again I'm trying not to introduce the concept of a "thread" unless we're 100% sure that's the right concurrency model for Dafny).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess that's a difficult thing to determine, but I was thinking that if there is a specific pattern (
reads {}
) used for a specific use-case (calling externally exposed methods concurrently), it does help to name that use-case and remove the need for the pattern.I see. I'm a little confused by the file
text/guide-level-explanation-example-1.dfy
which has similar but different content that's what's inline intext/0000-reads-clauses-on-methods.md
. The former hasreads this
but the latter one doesn't.Given that
FibonacciMemoizer.Get
doesn't take any reference type parameters, isn'treads this
the upper bound of what it can read soreads this
can be implicit?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's true. I could imagine something like
{:threadsafeExtern}
, or some way of parameterizing{:extern}
. It wouldn't eliminate the need forreads
clauses in general in order to verify this property, though. You'd have to either have that annotation implyreads {}
or require that such methods also declarereads {}
explicitly, and I'd likely prefer the latter.Given
{:extern}
is currently very weakly specified and already has several other sharp edges (https://github.com/dafny-lang/dafny/labels/foreign-function-interface), it's my preference to leave this out of scope for now, just as we also want to leave any concurrency semantics for Dafny out of scope if we can.Ah that's my fault for letting the two versions get out of sync. I was hoping to find a way to include the source files into the markdown. I've deleted them and put the missing
reads this
clause into the example.Some classes will only have
const
fields, so some class methods can declarereads {}
rather thanreads this
. I'm still thinking making the defaultreads *
everywhere would be better for simplicity.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that was what I was thinking of
I think I agree that
reads
clauses on methods are the more lightweight feature to add since it doesn't introduce any new concept.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're okay with a keyword like
threadsafe
, then the language could say the following:threadsafe
is not allowed to have areads
clause. Such a method is allowed to read anything (just like today).threadsafe method
is allowed to have areads
clause. We now even have the freedom to say that the defaultreads
clause for athreadsafe method
is the empty set. This would make it consistent with bothreads
andmodifies
clauses today. Furthermore, this would completely address my concern about beginning users getting the wrong idea that they must declare thereads
set for method -- if they ever see, or try to declare, a method with areads
clause, then that method is declared asthreadsafe method
, which the user can identify.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want the first
n
Fibonacci numbers, you wantAs written, you'll get the first
n + 1
Fibonacci numbers, except the first one.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest moving this type definition down below the
ExternalMutableMap
. I found myself reading it and the sentence above it several times, trying to figure out their connection, until I realized the sentence above has nothing to do withdatatype
s and that thisdatatype
is introduced only to support the example.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'd expect a nonempty
modifies
clause forPut
. Addmodifies this
.This also means that
FibonacciMemoizer.Get
on L139 needs amodifies
clause. (Without the usualRepr
/Valid()
abstraction, this can be done withmodifies cache
. With this change, it would then be fair to clients to add the postconditionensures fresh(cache)
to theFibonacciMemoizer
constructor.)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Believe it or not, no! The entire point here is to NOT model the external state in Dafny frames at all. To Dafny, an
ExternalMutableMap
is a completely opaque object, and its methods have non-deterministic behavior. This meansthis
has no mutable state and addingmodifies this
will just add confusion and probably more verification difficulties.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cache
is implicit forthis.cache
, so it readsthis
, right? Then the call inSafeFibonacci
would not compile.Or are you saying the
reads this
is not necessary becausecache
isconst
? But I would think we don't know whetherExternalMutableMap
is mutable or not, so even if the field isconst
, that doesn't mean reading it is thread-safe.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's right,
reads
andmodifies
only care about mutable fields, sovar
s are in scope but notconst
s. I agree that's a bit surprising but already howreads
works on functions.Sure, we don't "know" if
ExternalMutableMap
is mutable since the verifier has no visibility into the external code. But there's no way around that, and modelling the assumptions we might make about external code using ghost state as in https://github.com/dafny-lang/dafny/wiki/Modeling-External-State-Correctly breaks down if that external state can change without Dafny being aware of it. The idea here is to verify that a method does not interact with any mutable state on the Dafny heap, but may still interact with "the outside world", and it's up to the external code to be thread-safe when necessary.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And if
ExternalMutableMap
was not implemented externally, how would you know that it's immutable?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed this offline to understand the question better, and I can clarify how this all works with a few more details.
Note that clauses such as
reads
andmodifies
are not recursive. Take these classes as an example:If you had an object
a: A
, the clausereads {a}
would only allow you to referencea.foo
, but NOTa.b.foo
. For the latter to be allowed, you would needreads {a, a.b}
. This is why theValid() / Repr
idiom described in Chapters 8 and 9 of Using Dafny, an Automatic Program Verifier is useful for tracking what areas of the heap your code needs to read and write, without depending on the internals of your components.That means Dafny doesn't allow the
cache.Get
call because it believesExternalMutableMap
is immutable, it's allowed because thecache.Get
method hasreads {}
, which means the method does not read any state in the Dafny heap.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw, I presume that
reads
clauses are subject to themselves. That is, areads
clause has to be "closed", in the same way that they are for functions. Their well-formedness should then also be allowed to assume the method precondition, just as for functions. (The Dafny verifier has an encoding for this kind of thing. Look for the parameter calleddelayedReadsChecks
or something like that.)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sentence about
function method
does not seem relevant. (Functions already havereads
clauses, and details of where the lettersmethod
appear (perhaps in comments?) have no bearing on the RFC.) I suggest dropping the sentence.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The parser already disallows
modifies
clauses onlemma
declarations. The parser could do something similar forreads
.