-
Notifications
You must be signed in to change notification settings - Fork 25
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
VDMJ POG misses mutual recursion #15
Comments
Still in 2.0.0b2 |
|
|
|
|
Still in 2.0 |
Please add test case! |
Here you go...
That produces two subtype POs (for the a-1 on a nat), but no recursive POs. But we ought to prove that the mutually recurive f/g call sequence terminates by using their measures (via a PO). |
This ought to be possible by creating a variation of the cyclic dependency searching that the type checker does - mutually recursive function calls look like cyclic dependencies (in fact they are unless they are protected by conditionals). So we probably need a slightly different visitor to search for function applications (regardless of conditionals), then find loops (possibly of n>2?) and produce the POs from that. |
I think the POs we should produce relate to the measure for the "current" function compared to that for the next in a mutually recursive cycle. This is a generalisation of what Augusto proposed in his MSc thesis: that considers mutual recursion between two functions, and describes a single PO that has no caller context:
This is valid, but it seems more natural to generate one obligation for each recursive call in the context of each caller. So the example would generate two POs:
This then extends in a natural way to recursive cycles with an arbitrary number of functions, though it also changes the definition of what constitutes a recursive function (something that requires a measure), so the type checker has to change as well as the POG. |
This is now working in VDMJ (4.3.0 build 200101 and later). Recursion has been generalised to be a cycle of functions that can call each other in a loop, with the simple case of a function calling itself as a minimal case. In general, if a function can be part of a cycle like [f, g, h, f, g, h...] then all members of the cycle ought to have a measure defined (a warning if not). Note that a single function can be a member of multiple cycles, because it may call several other functions that call back to it. The warnings indicate the cycles concerned (since it can be hard to see!). In general, the test is that the measure value of the current invocation, is greater than that of the measure of the next function in the cycle, at the point that the 2nd function is called. The POs generated reflect this. Currently, the runtime test of measures only tests the decreasing measure value of the current function (like simple recursion). This is because the admin overhead of tracking multiple measures for different cycles each on possibly different threads could become prohibitive. Since a cycle, by definition, returns to itself at regular intervals, and since all measures must be monotonically decreasing, and since each function in the cycle is tested, this is a reasonable compromise. But it means a recursion that has "gone rogue" might make one more loop before the measure error is caught at runtime. A new error has been introduced since you cannot meaningfully compare measures from two functions in a cycle unless they all either return "nat" or the same sized nat tuple. So all of the measures in a cycle must now have the same return type. The extra type checking overhead for calculating and analysing recursive cycles does not seem to be too onerous, though it is naturally greater for larger specifications. I will start to port the change over to Overture's visitors... |
I've just pushed a fix for this to ncb/development. It is basically the same as the fixes for VDMJ, though there are a couple of problems still. One is that the visitor processing in Overture is somewhat slower than VDMJ, so (the worst example) the NewspeakSL test, which is nearly 3000 lines and is highly recursive, takes about 45 seconds to typecheck. It identifies large numbers of mutually recursive cycles, none of which has a measure. VDMJ checks this in about 7 seconds. To help with this, I've added a "skip.recursion.check" property which just does "simple" recursive checks, if set (ie. previous functionality). The second problem is with VDM++ where, in some cases, it does not know the fully qualified type of an apply expression (like "name(nat, real)"), when the arguments make forward reference to definitions that are not yet resolved. In these cases, it fails to spot the recursive call currently. I'm trying to find out how to get round this (in VDMJ it is easier because the LexNameToken used in the apply is the same as the one used in its definition, so when that is updated, so are all of the copies). I'll do some more testing and try to fix the problem above. |
So now the 2nd problem is much better. I'm now looking up the name of the apply to get the resolved name before returning it as part of a cycle. That seems to solve it for the examples in the tests. Still testing. |
So this seems OK. I'm going to mark it as mergable, though it really needs thorough testing before release. |
The algorithm was still missing some complex loop cases (now fixed), but this means that it takes even longer to check all of the possible loops in complex specs. I've now put a depth limit on the search: it will only identify loops that are less than 8 calls long (plus the original), for example:
This now checks complex specs in a fraction of a second. If you have loops longer than this... you won't get a warning. |
VDMJ's PO generator does not correctly identify mutually recursive functions, and so does not generate the associated POs (simply recursive functions are fine).
The text was updated successfully, but these errors were encountered: