3 ms·
As a simple example, let’s consider trying to establish semantics for stray (i.e. out of bounds) read and writes. We can start by trying to define what happens
by nkurz 11y ago
As a simple example, let’s consider trying to establish semantics for stray (i.e. out of bounds) read and writes. We can start by trying to define what happens for a stray read. That’s fairly easy, we can simply return an undefined value.
I don't disagree with this piece, but I think it's missing the thrust of the "friendly C" argument. The desire (at least I feel it) is simply for the compiler to "do what I mean", rather than making unexpected optimizations based on undefined behavior. There is no expectation that the resulting language will be "safe" as the word is normally used. If one wants safety (and can accept the performance compromise that this entails) then there are alternative languages to choose from.
Instead, "friendly C" has much simpler ambitions: replace undefined behavior with compiler specified behavior. The spec could simply guarantee that if you read an out-of-bounds value, that the compiler will generate assembly that attempts to read the value at that address. Having the program segfault on the attempt is perfectly acceptable. The only thing that is not acceptable is for the compiler to reason that the read will be out of bounds, and thus decide to omit the error checking code that follows.
- pcwalton 11y ago> The spec could simply guarantee that if you read an out-of-bounds value, that the compiler will generate assembly that attempts to read the value at that address You didn't understand the example. Your proposal destroys load-load forwarding, as the article demonstrates.
- anon4 11y agoWhy not extend the language with an "rmem" keyword or function that you use like this: rmem(pointer) and you're guaranteed to produce a memory read, rather than reusing a value in register. Then say that the compiler is free to assume two pointers/arrays etc. never overlap. Then, introduce linters which can detect most cases of these-two-pointers-might-overlap. Maybe runtime ones like valgrind. I want to write C as high level assembly. If I put values in registers and reuse them, then I'm opening myself for the same kind of problems as if I'm writing C and not doing explicit memory reads.
- pcwalton 11y agoYou have to allow pointers and arrays to overlap. Just to name one of tons of examples, you couldn't write the callback compar(a, b) to qsort() without it: the two pointers a and b are part of the same array and therefore a[1] might legally alias b[0], for example. There is something to controlling aliasing, though. It basically leads you to Rust's (or ISO Core C++ with lifetime profile) memory model. :)
- nkurz 11y agoYou didn't understand the example. Certainly this may be true, but I've re-read it again with your prompting and still think I understand it. Your proposal destroys load-load forwarding, as the article demonstrates. I'm fine a compiler that optimizes out the write to a value on the stack on the assumption that this can't be the same address as a passed pointer, whether that parameter is an int or float. I'm fine with a compiler that creates assembly that simply returns 0 here, with or without 'restrict'. That you and the author think this example represents a conflict with John's "friendly C" proposal makes me more certain that you don't mean the same thing by "friendly C" as I do, or as I think John does. Really, I think we're asking for something quite different, and much simpler to achieve.
- pcwalton 11y agoAre you proposing that compilers emit actual load instructions for every memory access you write in your code? That is, preventing the compiler from optimizing out memory loads? I can't read your comment any other way, but I may have missed something. In any case, if you require that a compiler emit an actual load instruction for every memory access then you've destroyed SROA, which is one of the most important optimizations, especially in C++. Without SROA C++ can easily be 4x-5x slower.
- nkurz 11y agoAre you proposing that compilers emit actual load instructions for every memory access you write in your code? No, if the compiler can reason that that there is no aliasing between pointers, there is no need to emit a load. Reordering operations so a value remains in registers is desirable, even if an out-of-bounds write would otherwise have had the side-effect of changing the value. The problem (in this context) is limited to optimizations that reason that the value of the read is "undefined" (as opposed to "implementation defined"), and that the compiler no longer has any obligation to be faithful to the source code for any actions that follow. For example (conceptual rather than exact) if I set all the bits in a region to 1's, and then somehow manage to write an unaligned load from within this region, I do not want the compiler to reason that this value is undefined and that all consequent code can be ignored. I'm fine with a compile time error, and I'm fine with generating assembly that attempts the load and uses the result. Getting SIGBUS for the load on some processors is perfectly acceptable. I'd probably even be OK with skipping the load and using a constant, although I'd hope this would at least be accompanied by a warning message. What I don't want is for the compiler to silently omit the load and all the rest of the code within that function on the theory that once undefined behavior has been encountered it no longer has any obligations, not even the obligation to inform the programmer that it has perversely optimized the function to nothingness. (And while most of the lack of clarity is probably my own fault, I do now realize that I was being thrown off by an unfortunate typo in the original post: presumably he meant for the second argument to be a 'float *' rather than a 'float'. Please ignore my comment about 'stack variables' higher in the thread.)