J. David Bryan wrote:
On 22 Nov 2002 at 2:17, Frank Heckenbach wrote:
As just explained, variant checking is not part of general range checking....
Perhaps I should have said, "part of a general implementation of _constraint_ checking...." It would indeed be useful to have several independently specifiable checking options for special circumstances, but I should think that, from a user perspective, whether:
rcd.twobyte := something^ + 1;
...fails from the result being out of range, or the variant selector value being wrong, or "something" being nil, or the addition overflowing is of secondary importance to catching the error at all.
For a programmer with "good intentions", that's probably true. But seeing that there are some (also in the "standards camp") who like to misuse variant records for type conversions, they'd probably want to turn off those checks.
And, of course, the implementation cost/benefit ratio might look quite different for the different kinds of checks. Range checks (in the strict sense) seem to be quite essential for many (and the good news is that I'll probably implement them soon), whereas I'm not so sure how many would benefit from variant checks (I think in my own code I hardy ever change a selector once a variable is allocated and initialized).
a.b := 2; a.b := 1; WriteLn (a.c) { WRONG }
But _is_ that wrong?
ISO 10206, section 6.5.3.3, says:
"When a variant becomes non-active, all of its components shall become totally-undefined."
But "totally-undefined" is not a value-bearing state, from 6.2.4, so the value of "a.c" is not changed by the change in the variant selector. Once "a.b" is set to 1, the first variant becomes active again, so references to "a.c" are again legal. On what basis, therefore, is the third statement above wrong?
References to a.c are valid again, but the state of a.c is (still) totally-undefined. So it follows, to my understanding, that reading a.c is invalid, until it is written to again.
For the occasional tight loop where speed really matters, the programmer might want to choose types etc. in order to help the compiler avoid unnecessary checks, rather than hoping for some complicated optimizations.
Or perhaps allow checking to be turned off and on via compiler directives.
Of course, they can also do that. (Though I prefer myself to avoid any compiler directives when the same can be achieved otherwise, e.g. by using an appropriate subrange type for an array index.)
Not exactly. In a `case' statement, the compiler produces code for all variants at once (possibly using jump tables etc.), for a variant access we need to check only one case.
I fear that again I have failed to make myself clear. A variant field access would, of course, check only the particular case-constant-list associated with that variant. What I meant was that the logic used to generate a _single_ case alternative comparison is the same (or similar) logic as is needed to generate a variant selector check. Consider:
program p;
var a, b : integer;
begin case a of -9999, 1..5: b := 2 end end.
Not a jump table in sight in the GPC-generated assembly code. ;-) My point was that handling variant selector checking isn't overwhelmingly difficult, as the problem is similar to handling case-constant-lists in regular CASE statements.
Of course, in this case a jump table would make no sense. My point was also that handling variant selector checking isn't overwhelmingly difficult -- just that we might rather reuse some other code than what you suggested.
I doubt whether that's too useful. The cases where the function call overhead might be worth it (very complex lists of selectors/ranges) are probably very rare in practice, so it doesn't seem worth optimizing for them.
Whether the check is inlined or a function call is simply an implementation issue.
Of course. Aren't we talking about implementation issues here? ;-)
I would imagine this is done simply to limit the code size (consider inlining the identical check hundreds or thousands of times).
For a single value or range, an explicit check is hardly more code than a function call. And I guess more complicated case lists are quite rare.
I have not investigated specifically, but I would not be surprised if specifying "-finline-functions" would change this behavior in GNAT.
I guess so.
Here, let's please stay at the high level.]
I agree in principle. However, it is my observation that programmers today generally have little knowledge of the code generated by compilers for specific constructs. I see this commonly expressed in erroneous assertions regarding the performance of specific compilers or language constructs, e.g., "Ada (or Pascal :-) generates inefficient code when compared to C," when an examination of the code generated for equivalent programs reveals otherwise.
Of course, this behaviour of some programmers is not new. I've seen years ago some proudly presenting in a newsgroup a hand-optimized assembler coding for `Abs' -- which turned out worse than the code that BP produced (which is not exactly famous for its sophisticated optimizations ;-). Apparently he had not even checked it before rolling his own.
Also, it's well-known that most "amateur optimizers" spend a lot of effort optimizing in the wrong places, those that are not time-critical at all. (A compiler can afford to optimize everything, but hand-coded assembly is only worth the effort in few places, and doesn't even make a measurable difference is most other places.)
I have attempted to show that variant checks are no harder in principle than range checks, but I see that I have been unsuccessful.
In general I agree. Compared to range checks, there's one thing that makes variant checks easier to implement -- it's immediately clear when to apply then (whereas range checks occur in various situations) -- and several things that make them more difficult:
- several case constants/ranges (just a little harder to do)
- otherwise branch (needs a negation of all other branches)
- no explicit tag-field (quite a bit harder, as I described)
- checks for accessing undefined fields (a topic of its own, probably quite hard in general)
Frank