Earlier quoted context omitted.
And that ABI specific stack manipulation would be UB, so the optimizer can assume it doesn't happen. With no UB, that's not true.
You assume a langue without UB would allow stack manipulation. But then it would have to be defined. How would you possibly define it fully?
Modern C (2019)
31–40 of 57 posts
Re: Modern C (2019)
#32Earlier quoted context omitted.
I'm okay with undefined behavior, but the language should revert back to the original (an exhaustive list of permissible behavior by the host when the C abstract machine's behavior is undefined) rather than the current mere list of examples of possible behavior by the host.
"I'm okay with undefined behavior, as long as its behavior is well-defined." You keep using that word. I don't think it means what you think it means. :)
To put that another way, you can treat certain behaviors as if they're opaque functions until after you're done optimizing. Similar to how you might handle volatile.
Some of these changes would be very easy. For example, instead of treating "dereference zero" as unreachable code, treat it the same as "dereference unknown location".
Re: Modern C (2019)
#33Earlier quoted context omitted.
Standard Forth, to my knowledge, doesn't have UB and consequently doesn't allow the optimization. That'd just be : foo CELL + 20 SWAP ! ; there. Machine languages also generally don't have this kind of UB, and don't allow the optimization. It's not impossible to imagine a C without UB, it's just not a particularly desirable language.
Parameters don't really work the same way in Forth. A similar C program would explicitly pass in an entire stack every time it runs a function, or have a global stack variable. And such a C program would disallow the optimization whether or not you consider UB. Machine language means you have basically no rules imposed on your code at all. So it's true that you can't optimize anything, but I think that's outside the…
Dunno what you mean about having no rules imposed on your code -- you can define the semantics of the language formally and execute them on a machine, same as you can for C, JavaScript, or Prolog.
Re: Modern C (2019)
#34Earlier quoted context omitted.
You assume a langue without UB would allow stack manipulation. But then it would have to be defined. How would you possibly define it fully?
There's an area of memory into which variables with automatic storage duration are allocated at implementation-defined addrs, at any point exactly those variables have addresses.
That doesn't sound like it lets me increment any stack address I want and store into the resulting pointer.
And are return values still on the same stack? I probably should have said I meant the traditional kind of C stack.
Re: Modern C (2019)
#35Earlier quoted context omitted.
Parameters don't really work the same way in Forth. A similar C program would explicitly pass in an entire stack every time it runs a function, or have a global stack variable. And such a C program would disallow the optimization whether or not you consider UB. Machine language means you have basically no rules imposed on your code at all. So it's true that you can't optimize anything, but I think that's outside the…
In my example, the variables are assumed to be addressable, only the address of x was passed via the stack. Dunno what you mean about having no rules imposed on your code -- you can define the semantics of the language formally and execute them on a machine, same as you can for C, JavaScript, or Prolog.
Yeah but I could add 3 billion to the pointer instead. Now what happens, if we're trying to define behavior?
> you can define the semantics of the language formally
If the semantics aren't a direct translation into CPU opcodes, then it's not machine code. And CPU opcodes can access all memory at all times including self-inspection so you can't change anything for optimization purposes without an additional framework on top.
Re: Modern C (2019)
#36Earlier quoted context omitted.
In my example, the variables are assumed to be addressable, only the address of x was passed via the stack. Dunno what you mean about having no rules imposed on your code -- you can define the semantics of the language formally and execute them on a machine, same as you can for C, JavaScript, or Prolog.
> In my example, the variables are assumed to be addressable, only the address of x was passed via the stack. Yeah but I could add 3 billion to the pointer instead. Now what happens, if we're trying to define behavior? > you can define the semantics of the language formally If the semantics aren't a direct translation into CPU opcodes, then it's not machine code. And CPU opcodes can access all memory at all times inc…
The cell whose address is three billion greater, mod the size of the address space, now has a value of 20?
> If the semantics aren't a direct translation into CPU opcodes, then it's not machine code.
Just because the semantics are defined that way doesn't necessarily mean you couldn't make another implementation that obeys those semantics. For instance, QEMU can perform optimizations on the code it runs, and invalidate them if the program changes those instructions. The semantics of reading from and writing to memory are unchanged by these optimizations, and the implementation doesn't need to do anything special on read.
Re: Modern C (2019)
#37Earlier quoted context omitted.
There's an area of memory into which variables with automatic storage duration are allocated at implementation-defined addrs, at any point exactly those variables have addresses.
> at any point exactly those variables have addresses That doesn't sound like it lets me increment any stack address I want and store into the resulting pointer. And are return values still on the same stack? I probably should have said I meant the traditional kind of C stack.
Why not?
> And are return values still on the same stack?
Other implementation-defined things might have addresses too, those things just aren't variables. I think you might be able to still allow inlining without UB if you make it implementation-defined per call-site what other things might get addresses.
Re: Modern C (2019)
#38Earlier quoted context omitted.
> In my example, the variables are assumed to be addressable, only the address of x was passed via the stack. Yeah but I could add 3 billion to the pointer instead. Now what happens, if we're trying to define behavior? > you can define the semantics of the language formally If the semantics aren't a direct translation into CPU opcodes, then it's not machine code. And CPU opcodes can access all memory at all times inc…
> Now what happens, if we're trying to define behavior? The cell whose address is three billion greater, mod the size of the address space, now has a value of 20? > If the semantics aren't a direct translation into CPU opcodes, then it's not machine code. Just because the semantics are defined that way doesn't necessarily mean you couldn't make another implementation that obeys those semantics. For instance, QEMU can…
Cool. It sounds like you figured out a restricted form of pointers for stack variables, since there's no danger of corrupting the code or breaking important invariants elsewhere.
Now imaging tightening those restrictions a bit. Instead of wrapping pointers inside the entire stack, wrap/constrict pointers inside their original variable.
Now you have a language with no UB that allows the optimizer to replace y with 42!
TL;DR: If you let pointers go hog-wild, your language has UB even before you consider optimizing. You don't need to add UB to allow optimizations. If you don't let pointers go hog-wild, in a language without UB, then you can enhance those restrictions to allow the y->42 optimization while still not having UB.
> For instance, QEMU can perform optimizations on the code it runs, and invalidate them if the program changes those instructions.
The machine can take shortcuts, and when QEMU is acting as a virtual machine it can do that. But the compiler can't take any shortcuts, because it never knows when external code is going to examine random bytes and need them to be unchanged.
Re: Modern C (2019)
#39Earlier quoted context omitted.
> at any point exactly those variables have addresses That doesn't sound like it lets me increment any stack address I want and store into the resulting pointer. And are return values still on the same stack? I probably should have said I meant the traditional kind of C stack.
> That doesn't sound like it lets me increment any stack address I want and store into the resulting pointer. Why not? > And are return values still on the same stack? Other implementation-defined things might have addresses too, those things just aren't variables. I think you might be able to still allow inlining without UB if you make it implementation-defined per call-site what other things might get addresses.
At this point the conversation has completely converged so I'm only going to reply over here: https://news.ycombinator.com/item?id=36169808
Re: Modern C (2019)
#40I enjoy programming in C a lot, though I wish they would fix C's biggest mistake[1]. [1] https://digitalmars.com/articles/C-biggest-mistake.html >
C biggest "mistake" (there are historical reasons for this, though, but it doesn't make it acceptable nowadays) is the concept of undefined behavior, period.
Signed overflow was not intended to be undefined--it was unspecified because it has a perfectly acceptable specification that differs between processors, and it was expected that the compiler would define it. The fact that gcc and clang pick non-sensical specifications in order to gain 3% in performance was not something anybody expected back when this stuff was written.
"Division by zero" is, on the other hand, genuinely undefined behavior. "Dereferencing a NULL pointer" is a genuinely undefined behavior. There generally isn't a good way for a program to continue after encountering them.