last day (16 days later) » 

22:16
-2
A: Pros and cons of "anything-can-happen" UB versus allowing particular deviations from sequential progran execution

André L F S BacciIf there are some know or expected particular deviations, then these deviations are defined somewhat. But the thing with UB is that it really means undefined, anything goes. See some example below. Focusing entirely on the title and the last paragraph: What advantages are there to the currently-...

In what way would the specification #2 in the question not be superior to the former approach for declaring "pure" functions. Present compilers may not be capable of usefully exploiting such semantics, but that's no reason why future languages shouldn't be designed better.
I don't think this answer adds any valuable information. We already know that UB means anything can happen, the question is what the pros/cons are for letting this happen.
@G.Sliepen: My point is that a well-written spec shouldn't say "anything can happen" in all situations where a useful optimizing transform might result in a program behaving in a manner which is inconsistent with sequential program execution but may still meet application requirements. If a programmer writes do { x=foo(1); y=bar(z); ...} and both function calls generate logs but are otherwise side-effect free, having N executions of the loop result in one log entry for bar followed by N entries for foo would be inconsistent with sequential program execution, but ...
...any combination of log entries for calls to foo and bar would satisfy application requirements equally well, granting a compiler the freedom to choose among many behaviors that all satisfy application requirements would be more useful than saying that the only way to guarantee anything is to explicitly force everything.
@G.Sliepen You can't avoid “letting” this happen unless you can ensure that nothing slips through the cracks. In a language without memory safety, it's impossible to guarantee that nothing slips through the cracks.
@supercat such a spec would make assumptions about the underlying platform that c doesn't provide. For example, there exist c platforms (without any optimisation) where a ub pointer dereference can result in a motor spinning up.
22:16
@user1937198: On an Apple II with a floppy controller in slot #6 (the usual place), reading address $C0E9 will turn on the floppy drive motor, and reading address $C0EF while the motor is running will trash whatever track is under the drive head. From a language perspective, however, one could specify a "low level C" dialect where read-dereferencing a char volatile* holding address $C0E9 would synchronize the abstract and physical machine states and then instruct the environment to perform a load from address $C0E9. The language spec wouldn't care about what the environment...
...would do when instructed to perform the load, and a programmer who didn't know how the environment would process the load would have no way of knowing what the consequences might be, but a programmer who knew what the environment would do in response to such a load instruction would be entitled to rely on that behavior. An access not qualified volatile would be somewhat similar, except that accesses might sometimes be "cached", hoisted, deferred, and/or consolidated.
@supercat now I have a pointer to a char array at $1000, and attempt to write out of bounds to index $B0EF, how do you stop that trashing the disk without runtime bounds checking?
Without memory safety, you can't prevent arbitrary memory load/stores, and without constraints on memory mapped io, you can't stop arbitrary load/stores having arbitrary effects. And thus any language that does not have memory safety, and does not place constraints on memory mapped io, must have nasal demon ub.
And there are other possibilities of escape. Without WxE protection, an invalid memory write could modify code, which when executed would have behaviour dependent on the machine code of the system, and could do anything that is possible in machine code.
You can list the explicit transforms under which a write can be moved, but if a write can result in arbitrary behaviour when out of bounds, and that behaviour can be hoisted, then you are effectively allowing time traveling undefined behaviour. It is entirely possible for a hoisted out of bounds write to attempt to write to memory that would corrupt control flow between the ordinal write point and thehoist point. At which point anything can happen.
@user1937198: In typical low-level dialects of C, the array access would be processed as a load or store from/to address $C0EF; if the execution environment dopcuments the effect of such access, a low-level implementation would process it "in a documented manner characteristic of the environment". The Standard shouldn't concern itself with what the environment's documented characteristic action would be, or whether it would be desirable or undesirable.
@supercat and to do so is a) implementation defined, and b) either limits your ability to move around accesses, as effectively all accesses must be treated as volatile unless proven otherwise, as all accesses have the potential to break memory safety, or you effectively dump the documentation of the optimizer as your implementation defined behaviour spec, and end up with a spec so complex no one understands it
The issue with undefined behaviour vs implementation defined behaviour is that undefined behaviour usually comes from at least a platform where there is a potential subset of that behaviour, where if executed, even with no transforms, the compiler can no longer guarantee the runtime state of the program at all.
You are escaping the abstract model, and a sufficiently detailed implementation spec would be the ISA spec and associated translation details, along with any optimisations. Consider the case of an out of bounds write of program code, the only way to define it is to include how the write translates to machine code, and what machine code is already present.
@user1937198: How does the Standard seek to characterize actions which most implementations would process in the same useful fashion, but where on a few implementations it would be expensive to guarantee any behavior consistent with sequential program execution, and no possible behavioral spec would be useful? Classifying an action as "Implementation-Defined" means that all implementations must document an action consistent with the overall abstraction model.
@user1937198: //either limits your ability to move around accesses, as effectively all accesses must be treated as volatile unless proven otherwise// I specifically allowed for the possibility that compilers may reorder memory accesses, subject to various rules. Making decisions about whether memory accesses may be reordered based upon static aspects of program structure is vastly easier than trying to reliably make determinations based upon dynamic aspects of program execution. If one says that programmers requiring that actions occur in a particular sequence must ensure...
...that there is at least one rule that would force the accesses to occur in that sequence would seem much saner than having rules which define actions whose behavior is obviously meant to be defined as equivalent to actions whose behavior is expressly characterized as undefined.
Due to the politics of the C/C++ standards, its usually an all or nothing of undefined vs implementation defined behaviour.
allowing reordering subject to rules is the problem. what if the write modifies the code under execution to such that on a specific microarchitecture it results in a jump to non-code between the original and reordered write. You now have an apparently arbitrary subset of the code not being executed, and something completely different being executed instead. And so rather than trying to cover all these cases, the standard just goes, if you break memory safety, all bets are off.
23:05
@user1937198 If the rules allow a variety of reorderings, but all no allowable orderings would result in out-of-bounds memory accesses, what's the problem?
I wonder what range of answers committee members would give if asked "What jurisdiction does Standard exercise over programs that are not intended to be 100% portable?"
23:17
As for "breaking memory safety", I would be more specific: an implementation is allowed to use any storage which it has received from the environment, but which has no C-language meaning, in any manner it sees fit, and may thus behave in arbitrary storage if it is disturbed. It may likewise use abstract data types like FILE* or va_list as it sees fit. Address space which an implementation has not received from the environment, however, ...
...should be treated as having environment-defined semantics the programmer may know about even if the compiler does not.

  last day (16 days later) »