assert semantic change proposal

via Digitalmars-d digitalmars-d at puremagic.com
Sun Aug 10 08:24:13 PDT 2014


On Sunday, 10 August 2014 at 13:47:45 UTC, Timon Gehr wrote:
>> No, if you prove false that means either that it is 
>> nonterminating
>
> I.e. everything afterwards is unreachable.

Depends on your take on it. If you support non-deterministic 
computations (e.g. Ada's "select") then non-termination is 
treated like bottom. So you can have

infinite_recursion() or terminates() => terminates()

So unreachable in terms of machine-language, sure, but not in 
terms of execution flow.

> Do you agree we indeed have equivalence for those programs with 
> an empty precondition?

You mean "false" or "true"?

>> or that it cannot be proved by the the assumptions, hence the 
>> assumptions
>> need strengthening.
>> ...
>
> 'false' is the strongest statement. Why would you need to 
> strengthen assumptions?

If you succeed in proving the postcondition to be false then the 
postcondition does not hold and you have to strengthen the 
precondition or else the triplet is invalid/incorrect. If the 
precondition is defined false then the postcondition is not 
required to hold. It is not covered by the 
proof/verification/specification and the triplet is 
valid/provably correct.

So if the precondition is defined false then the program is a 
priori proved correct to the specification, i.e. the 
specification does not require any guarantees for the result.

> If the precondition is actually assumed to hold for all 
> executions of the program, a precondition of 'false' means that 
> the program never executes.

That is the responsibility of the caller. It is the caller's 
responsibility to uphold the precondition in order to get 
guaranteed results.

But you could have multiple pairs of 
preconditions/postconditions. E.g. you could for a generic 
function have different pairs for int/float/double or have a 
separate pair for termination, etc. Or have one set that 
specifies what is covered by unit-tests, what is vetted by 
review, what is totally unknown… You could have different types 
of guarantees for a library and users could take advantage of it 
based on the requirements for the application (high security vs 
computer game).

> Maybe you don't assume this, but internally to the logic, this 
> is what happens: You verify the postcondition under the 
> assumption of the precondition. I.e. in this case you verify 
> the postcondition of the program under the assumption that it 
> never executes.

It is not always possible to prove termination, so that would be 
unfortunate. It would be more useful to be able to restrict the 
precondition to what you cover by unit-tests. Or to have multiple 
sets of pre/postconditions (proven-by-machine vs 
vetted-by-review).

Then if you can prove that the caller fulfills the precondition 
you are able to optimize based on the pre/post condition and 
assume the post-condition if it is machine-verified.

That would give new optimization possibilities.

That does not mean that you should never be able to use a 
function outside the verified range (by machine proof), but then 
you need to assert the results at runtime if you want verified 
correctness.

> Of course you may still execute such a program, but the ⦃ ⦄ 
> assertions become irrelevant to this execution.

Not really, they could still trap.

> Note that an optimizer needs to be able to use deduced facts 
> for program transformations, or it is useless. To an optimizer, 
> a contradiction means 'unreachable'.

No, to an optimizer a contradiction means "all bets are off for 
the postcondition to hold":

program(x) != executable(x)

That is why __assume(P) must always hold on any reachable path 
(e.g. any path that the optimizer considers to be a target for 
optimization).

But __assume is not a regular precondition, it has a counterpart 
in the optimization invariant (postcondition):

program(x) == executable(x), x not creating contradictions by 
__assume(…)

The optimizer is presuming that anything goes as long as this 
optimization invariant can be upheld. This is not a postcondition 
specified by the programmer at the meta-level. It is implicit.

And the optimizer does not check the implicit precondition, at 
all…

> to deduce those facts. Do you agree that _if_ 'assume' is to be 
> seen as an optimizer hint, then 'assume(false)' means 'assume 
> unreachable'?

No. I think the optimizer should only use assumptions when they 
are guaranteed to hold by the caller or the language axioms. Or 
in the rare case by other machine verifiable axioms 
(specifications for hardware registers etc).

>> Of course it does, that is why Hoare Logic and SSA exist.
>
> They exist for the virtue of being convenient formalisms for 
> some applications, but they can be embedded into e.g. set 
> theory just fine.

Not sure what you mean by this and how this relates to imperative 
programming.

>> Deduction lacks a notion of time.
>> ...
>
> What is 'a notion of time' and how does HL provide it?

It doesn't. Which is why it trips when you don't prove 
termination. But HL does not do program transformations.

> The assert(Q); statement is equivalent to
>
> if(¬Q) abort;

Not if assert() is considered meta-level. However, since a 
triplet is tied to an entity/function/module, it would be ok to 
throw an exception as long as it isn't caught in the module 
covered by the pre/postcondition triplet if it does not have any 
side effects.

If you don't reach the postcondition it does not matter.

>> Assert is not an imperative statement,
>
> Yes, it may be seen to be.

That would be an aberration, because the specification should 
either be independent of the implementation or generate the 
implementation…

If the specification is considered part of the program then you 
will run into problems if there are contradictions.

> assert(P); is a statement and hence it needs to be given a 
> semantics, e.g. an axiomatic semantics or by rewriting, as 
> above.

Well, in C it is a statement by implementation, but it is always 
meant to not have side effects and one should be able to compile 
with NDEBUG with no difference in program behaviour. So, even 
though it is implemented as a macro it has always had the status 
of an annotation.

> (BTW: This is not about being right or wrong. It is about 
> understanding each other's statements in the right context.)

I think the whole game changes when Walter wants to optimize 
based on the annotations… Then you need to be very careful unless 
you want to amplify bugs rather than improve the overall 
performance of the program (speed + correct behaviour).

So then the distinction between specification and implementation, 
proven and vetted, becomes even more important.

Since D has unit tests as a formalism, then it would be a shame 
to not use it to verify contracts in a way that could provide 
safe optimization possibilities rather than relying on 
programmers being able to write provably correct code in a 
predictable manner, which just ain't gonna happen.


More information about the Digitalmars-d mailing list